diff --git a/.gitmodules b/.gitmodules index d2b4212..10c198d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "extern/CodeAnalysis"] path = extern/CodeAnalysis url = https://github.com/feast107/CodeAnalysis.git +[submodule "extern/IL"] + path = extern/IL + url = https://github.com/Antelcat/IL.git diff --git a/extern/IL b/extern/IL new file mode 160000 index 0000000..e8bc8b8 --- /dev/null +++ b/extern/IL @@ -0,0 +1 @@ +Subproject commit e8bc8b87244049a5291f6a9cd96250b83345bf6a diff --git a/src/Antelcat.ClaimSerialization.Tests.Runtime/IdentityModel.cs b/src/Antelcat.ClaimSerialization.Tests.Runtime/IdentityModel.cs index 571aa76..c4a5b8b 100644 --- a/src/Antelcat.ClaimSerialization.Tests.Runtime/IdentityModel.cs +++ b/src/Antelcat.ClaimSerialization.Tests.Runtime/IdentityModel.cs @@ -1,14 +1,16 @@ using System.Security.Claims; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using Antelcat.ClaimSerialization.ComponentModel; -using Antelcat.ClaimSerialization.Metadata; namespace Antelcat.ClaimSerialization.Tests.Runtime; public class IdentityModel { - public string? Name { get; set; } = nameof(Name); + + public string? Name { get; set; } = nameof(Name); - public int Id { get; set; } = 123456; + public int Id { get; set; } = 123456; [ClaimType(ClaimTypes.Role)] public ISet Roles { get; set; } @@ -24,4 +26,10 @@ public enum Role User, Guest } +} + +[JsonSerializable(typeof(IdentityModel))] +public partial class JsonContext : JsonSerializerContext +{ + } \ No newline at end of file diff --git a/src/Antelcat.ClaimSerialization.Tests.Runtime/RuntimeTest.cs b/src/Antelcat.ClaimSerialization.Tests.Runtime/RuntimeTest.cs index 0e2a10b..559a2da 100644 --- a/src/Antelcat.ClaimSerialization.Tests.Runtime/RuntimeTest.cs +++ b/src/Antelcat.ClaimSerialization.Tests.Runtime/RuntimeTest.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Text.Json; namespace Antelcat.ClaimSerialization.Tests.Runtime; @@ -22,13 +22,7 @@ public void TestRuntime() [Test] public void TestTypes() { - var list = new List() { IdentityModel.Role.Admin }; - var args = list.Cast(); - var method = typeof(Enumerable) - .GetMethods(BindingFlags.Public | BindingFlags.Static) - .First(x => - x.Name == nameof(Enumerable.Select) - && x.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>)); + JsonSerializer.Serialize(new object(),typeof(Type)); } private static bool Predicate(Type type) @@ -63,4 +57,21 @@ private static IEnumerable EnumAllInterfaces(Type type) yield return @interface; } } -} \ No newline at end of file + + [Test] + [Req(Arg = "")] + public void TestRef() + { + var a = (object)1; + + Func c = o => o; + Console.WriteLine(a); + } + +} + +public class ReqAttribute : Attribute +{ + public required string Arg { get; set; } +} +delegate void Setter(ref T target, T value); diff --git a/src/Antelcat.ClaimSerialization.sln b/src/Antelcat.ClaimSerialization.sln index 9d76a11..a19a205 100644 --- a/src/Antelcat.ClaimSerialization.sln +++ b/src/Antelcat.ClaimSerialization.sln @@ -27,6 +27,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Feast.CodeAnalysis.LiteralG EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Antelcat.ClaimSerialization.Tests.Runtime", "Antelcat.ClaimSerialization.Tests.Runtime\Antelcat.ClaimSerialization.Tests.Runtime.csproj", "{09C48496-D93E-4C1F-BE59-FAA226206B19}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Antelcat.IL", "..\extern\IL\src\Antelcat.IL\Antelcat.IL.csproj", "{1F3D864A-F3E5-48D9-85E0-FF1D6E9E77D9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -61,6 +63,10 @@ Global {09C48496-D93E-4C1F-BE59-FAA226206B19}.Debug|Any CPU.Build.0 = Debug|Any CPU {09C48496-D93E-4C1F-BE59-FAA226206B19}.Release|Any CPU.ActiveCfg = Release|Any CPU {09C48496-D93E-4C1F-BE59-FAA226206B19}.Release|Any CPU.Build.0 = Release|Any CPU + {1F3D864A-F3E5-48D9-85E0-FF1D6E9E77D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F3D864A-F3E5-48D9-85E0-FF1D6E9E77D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F3D864A-F3E5-48D9-85E0-FF1D6E9E77D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F3D864A-F3E5-48D9-85E0-FF1D6E9E77D9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -68,6 +74,7 @@ Global GlobalSection(NestedProjects) = preSolution {7184E740-24E8-4DB5-8484-A81B55DF4BB7} = {30C60FDF-F02F-45CF-88B0-1CDDF8596483} {A81EC26E-7A38-42B4-96CD-1B005BB40AB5} = {30C60FDF-F02F-45CF-88B0-1CDDF8596483} + {1F3D864A-F3E5-48D9-85E0-FF1D6E9E77D9} = {30C60FDF-F02F-45CF-88B0-1CDDF8596483} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FB0E866D-B450-4CBD-BEF0-224F0B1856B8} diff --git a/src/Antelcat.ClaimSerialization/Antelcat.ClaimSerialization.csproj b/src/Antelcat.ClaimSerialization/Antelcat.ClaimSerialization.csproj index a355263..ead41ed 100644 --- a/src/Antelcat.ClaimSerialization/Antelcat.ClaimSerialization.csproj +++ b/src/Antelcat.ClaimSerialization/Antelcat.ClaimSerialization.csproj @@ -52,9 +52,10 @@ - + + diff --git a/src/Antelcat.ClaimSerialization/ClaimSerializerContext.cs b/src/Antelcat.ClaimSerialization/ClaimSerializerContext.cs index 530d96f..f95e117 100644 --- a/src/Antelcat.ClaimSerialization/ClaimSerializerContext.cs +++ b/src/Antelcat.ClaimSerialization/ClaimSerializerContext.cs @@ -1,6 +1,7 @@ -namespace Antelcat.ClaimSerialization; + +namespace Antelcat.ClaimSerialization; public abstract class ClaimSerializerContext { - -} \ No newline at end of file +} + diff --git a/src/Antelcat.ClaimSerialization/Metadata/ClaimTypeInfo.cs b/src/Antelcat.ClaimSerialization/Metadata/ClaimTypeInfo.cs index 7074709..0cb1753 100644 --- a/src/Antelcat.ClaimSerialization/Metadata/ClaimTypeInfo.cs +++ b/src/Antelcat.ClaimSerialization/Metadata/ClaimTypeInfo.cs @@ -1,323 +1,52 @@ -using System.Collections; -using System.Reflection; -using System.Security.Claims; -using Antelcat.ClaimSerialization.ComponentModel; +using System.Security.Claims; using Antelcat.ClaimSerialization.Metadata.Internal; -using Antelcat.IL; -using Antelcat.IL.Extensions; namespace Antelcat.ClaimSerialization.Metadata; -public class ClaimTypeInfo +public abstract class ClaimTypeInfo { - protected class SetHandlers - { - public SetHandlers(SetHandler handler) => Handler = handler; - - public SetHandlers(SetHandler handler, - InvokeHandler> staticTransform) - { - Handler = handler; - Transform = s => staticTransform(null, s)!; - } - - public SetHandlers(SetHandler handler, Func, object?> dynamicTransform) - { - Handler = handler; - Transform = dynamicTransform; - } - - private SetHandler Handler { get; } - private Func, object?> Transform { get; } = s => s.First(); - - public void Set(object target, IEnumerable value) - { - var val = Transform(value); - if (val != null) Handler(ref target!, val); - } - } - - protected class GetHandlers - { - public GetHandlers(GetHandler handler) - { - Handler = handler; - } + protected abstract Constructor Constructor { get; } + protected abstract SetProperty[] Setters { get; } + protected abstract GetProperty[] Getters { get; } - public GetHandlers(GetHandler handler, Func> dynamicTransform) - { - Handler = handler; - Transform = dynamicTransform; - } - - private GetHandler Handler { get; } - private Func> Transform { get; } = Default; - - public IEnumerable Get(object target) - { - var val = Handler(target); - return val == null ? Array.Empty() : Transform(val); - } - - private static IEnumerable Default(object o) - { - yield return o.ToString(); - } - } - - private readonly Dictionary setHandlers; - private readonly Dictionary getHandlers; - private readonly CtorHandler ctorHandler; - - protected ClaimTypeInfo(Type type) - { - var ctor = type.GetConstructors().FirstOrDefault(x => x.GetParameters().Length == 0); - if (ctor == null) - { - throw new MissingMethodException("Can not find default constructor."); - } - - ctorHandler = ctor.CreateCtor(); - var claimProps = type - .GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(x => x.GetCustomAttribute(typeof(ClaimIgnoreAttribute)) == null) - .ToList(); - try - { - setHandlers = ResolveSetHandlers(claimProps).ToDictionary(x => x.Key, x => x.Value); - getHandlers = ResolveGetHandlers(claimProps).ToDictionary(x => x.Key, x => x.Value); - } - catch (ArgumentException e) - { - throw new ArgumentException($"Duplicate claim type: {e.Message}", e); - } - } public IEnumerable Serialize(object target) { - foreach (var values in getHandlers) + foreach (var values in Getters) { - foreach (var value in values.Value.Get(target)) + foreach (var value in values.Get(target)) { - if (value != null) yield return new Claim(values.Key, value); + if (value != null) yield return new Claim(values.Type, value); } } } + public object? Deserialize(IEnumerable claims) { - var target = ctorHandler(); + var target = Constructor.Create(); foreach (var claim in claims.GroupBy(x => x.Type)) { - if (setHandlers.TryGetValue(claim.Key, out var handler)) - { - handler.Set(target!, claim.Select(x => x.Value)); - } + var handler = Setters.FirstOrDefault(x => x.Type == claim.Key); + handler?.Set(target!, claim.Select(x => x.Value)); } return target; } - internal static ClaimTypeInfo GetTypeInfo() => GetTypeInfo(typeof(T)); - internal static ClaimTypeInfo GetTypeInfo(Type type) + internal static ClaimTypeInfo GetTypeInfo() { + var type = typeof(T); if (CacheContext.Default.CachedClaimTypeInfos.TryGetValue(type, out var info)) return info; - info = new ClaimTypeInfo(type); + info = new RuntimeClaimTypeInfo(); CacheContext.Default.CachedClaimTypeInfos.Add(type, info); return info; } - private static string GetClaimType(MemberInfo property) - { - var claimType = property.Name; - var attr = property.CustomAttributes - .FirstOrDefault(x => x.AttributeType == typeof(ClaimTypeAttribute)); - if (attr != null) - { - claimType = attr.ConstructorArguments[0].Value as string ?? property.Name; - } - - return claimType; - } - - protected static IEnumerable> ResolveSetHandlers( - IEnumerable properties) => - from property - in properties.Where(x => x.CanWrite) - let type = GetClaimType(property) - select new KeyValuePair(type, - ResolveSetHandler(property.PropertyType, property.CreateSetter())); - protected static IEnumerable> ResolveGetHandlers( - IEnumerable properties) => - from property - in properties.Where(x => x.CanRead) - let type = GetClaimType(property) - select new KeyValuePair(type, - ResolveGetHandler(property.PropertyType, property.CreateGetter())); - - private static GetHandlers ResolveGetHandler(Type propertyType, GetHandler getter) - { - if (propertyType == typeof(string) - || propertyType == typeof(object) - || propertyType.IsEnum - || CacheContext.StringConvertableSystemTypes.ContainsKey(propertyType)) - return new GetHandlers(getter); - - if (CanBeIEnumerable(propertyType, out var elementType)) - { - var select = GetSelect(elementType!); - return new GetHandlers(getter, x => select((IEnumerable)x)!); - } - - var serialize = Serialize(propertyType); - return new GetHandlers(getter, x => Yield(serialize(x))); - } - private static SetHandlers ResolveSetHandler(Type propertyType, SetHandler setter) - { - if (propertyType == typeof(string) || propertyType == typeof(object)) - return new SetHandlers(setter); - if (propertyType.IsEnum) - return new SetHandlers(setter, s => Enum.Parse(propertyType, s.First())); - - if (CacheContext.StringConvertableSystemTypes.TryGetValue(propertyType, out var handler)) - return new SetHandlers(setter, s => handler(s.First())); - if (CanBeIEnumerable(propertyType, out var elementType)) - { - var select = SetSelect(elementType!); - var to = MapTo(propertyType, elementType!); - return new SetHandlers(setter, x => to(select(x))); - } - - var serialize = Deserialize(propertyType); - return new SetHandlers(setter, x => serialize(x.First())); - } - - private static Func> GetSelect(Type elementType) - { - if (elementType == typeof(string) - || elementType == typeof(object) - || elementType.IsEnum - || CacheContext.StringConvertableSystemTypes.ContainsKey(elementType)) - return enumerable => - enumerable.Cast().Select(x => x?.ToString()); - - return enumerable => ((IEnumerable)enumerable).Select(Serialize(elementType)); - } - private static Func, IEnumerable> SetSelect(Type elementType) - { - if (elementType == typeof(string) || elementType == typeof(object)) - return enumerable => enumerable; - - if (elementType.IsEnum) - return enumerable => enumerable.Select(x => Enum.Parse(elementType, x)); - - if (CacheContext.StringConvertableSystemTypes.TryGetValue(elementType, out var handler)) - return enumerable => enumerable.Select(handler); - - return enumerable => enumerable.Select(Deserialize(elementType)); - } - - private static IEnumerable Yield(string o) - { - yield return o; - } - - private static Func Deserialize(Type valueType) => x => x.JsonDeserialize(valueType); - private static Func Serialize(Type valueType) => x => x.JsonSerialize(valueType); - - public static bool CanBeIEnumerable(Type type, out Type? elementType) - { - elementType = null; - if (type.IsArray) return true; - if (type.IsInterface) - { - if (Selector(type, out elementType)) - { - return true; - } - } - - foreach (var @interface in CacheContext.EnumAllInterfaces(type)) - { - if (Selector(@interface, out elementType)) - { - return true; - } - } - - return false; - - static bool Selector(Type t, out Type? elementType) - { - elementType = null; - if (!t.IsGenericType || t.GetGenericTypeDefinition() != typeof(IEnumerable<>)) return false; - elementType = t.GetGenericArguments()[0]; - return true; - } - } - - private static Func, object> MapToCollection(Type collectionType, Type elementType) - { - var add = CacheContext.Default.GetCollectionAdder(elementType); - var ctor = CacheContext.Default.GetCollectionConstructor(collectionType); - return x => - { - var set = ctor()!; - foreach (var o in x) - { - add(set, o); - } - - return set; - }; - } - private static Func, object> MapTo(Type containerType, Type elementType) + internal static ClaimTypeInfo GetTypeInfo(Type type) { - if (containerType.IsArray) // is T[] - { - var toArray = CacheContext.Default.GetToArrayHandler(elementType); - return x => toArray(null, x)!; - } - - if (containerType is { IsClass: true, IsGenericType: true }) - { - var genericTypeDefinition = containerType.GetGenericTypeDefinition(); - if (genericTypeDefinition == typeof(List<>)) // is List - { - var toList = CacheContext.Default.GetToListHandler(elementType); - return x => toList(null, x)!; - } - - if (CacheContext.SystemGenericCollectionTypes.Any(x => - x.MakeGenericType(elementType) == containerType)) // is System Generic Collection - { - return MapToCollection(containerType, elementType); - } - - // is custom collection - if (typeof(ICollection<>).MakeGenericType(elementType).IsAssignableFrom(containerType)) - { - return MapToCollection(containerType, elementType); - } - } - - if (containerType.IsInterface) - { - if (containerType.IsAssignableFrom(typeof(List<>).MakeGenericType(elementType))) // can be List - { - var toList = CacheContext.Default.GetToListHandler(elementType); - return x => toList(null, x)!; - } - - var type = CacheContext.SystemGenericCollectionTypes - .Select(x => x.MakeGenericType(elementType)) - .FirstOrDefault(containerType.IsAssignableFrom); - - if (type != null) - { - return MapToCollection(type, elementType); - } - } - - throw new ArgumentException("Not supported type."); + if (CacheContext.Default.CachedClaimTypeInfos.TryGetValue(type, out var info)) return info; + info = (ClaimTypeInfo)Activator.CreateInstance(typeof(RuntimeClaimTypeInfo<>).MakeGenericType(type)); + CacheContext.Default.CachedClaimTypeInfos.Add(type, info); + return info; } } \ No newline at end of file diff --git a/src/Antelcat.ClaimSerialization/Metadata/Constructor.cs b/src/Antelcat.ClaimSerialization/Metadata/Constructor.cs new file mode 100644 index 0000000..2acfe08 --- /dev/null +++ b/src/Antelcat.ClaimSerialization/Metadata/Constructor.cs @@ -0,0 +1,6 @@ +namespace Antelcat.ClaimSerialization.Metadata; + +public class Constructor(Func constructor) +{ + public object? Create(params object[] args) => constructor(args); +} diff --git a/src/Antelcat.ClaimSerialization/Metadata/GetProperty.cs b/src/Antelcat.ClaimSerialization/Metadata/GetProperty.cs new file mode 100644 index 0000000..72daadc --- /dev/null +++ b/src/Antelcat.ClaimSerialization/Metadata/GetProperty.cs @@ -0,0 +1,24 @@ +namespace Antelcat.ClaimSerialization.Metadata; + +public class GetProperty(string type, Func getter) +{ + public string Type { get; } = type; + + public GetProperty(string type, Func getter, + Func> dynamicTransform) : this(type, getter) => + Transform = dynamicTransform; + + private Func Handler { get; } = getter; + private Func> Transform { get; } = Default; + + public IEnumerable Get(object target) + { + var val = Handler(target); + return val == null ? Array.Empty() : Transform(val); + } + + private static IEnumerable Default(object o) + { + yield return o.ToString(); + } +} \ No newline at end of file diff --git a/src/Antelcat.ClaimSerialization/Metadata/Internal/ClaimTypeInfo.cs b/src/Antelcat.ClaimSerialization/Metadata/Internal/ClaimTypeInfo.cs deleted file mode 100644 index cc61b75..0000000 --- a/src/Antelcat.ClaimSerialization/Metadata/Internal/ClaimTypeInfo.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Antelcat.ClaimSerialization.Metadata.Internal; - -internal sealed class ClaimTypeInfo() : ClaimTypeInfo(typeof(T)); \ No newline at end of file diff --git a/src/Antelcat.ClaimSerialization/Metadata/Internal/RuntimeClaimTypeInfo.cs b/src/Antelcat.ClaimSerialization/Metadata/Internal/RuntimeClaimTypeInfo.cs new file mode 100644 index 0000000..e419184 --- /dev/null +++ b/src/Antelcat.ClaimSerialization/Metadata/Internal/RuntimeClaimTypeInfo.cs @@ -0,0 +1,230 @@ +using System.Collections; +using System.Reflection; +using Antelcat.ClaimSerialization.ComponentModel; +using Antelcat.IL.Extensions; + +namespace Antelcat.ClaimSerialization.Metadata.Internal; + +internal sealed class RuntimeClaimTypeInfo : ClaimTypeInfo +{ + protected override Constructor Constructor { get; } + protected override SetProperty[] Setters { get; } + protected override GetProperty[] Getters { get; } + + public RuntimeClaimTypeInfo() + { + var type = typeof(T); + var ctor = type + .GetConstructors() + .FirstOrDefault(x => x.GetParameters().Length == 0); + if (ctor == null) throw new MissingMethodException("Can not find default constructor."); + + var claimProps = type + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => x.GetCustomAttribute(typeof(ClaimIgnoreAttribute)) == null) + .ToList(); + var ctorH = ctor.CreateCtor(); + Constructor = new Constructor(x => ctorH(x)); + Setters = ResolveSetHandlers(claimProps).ToArray(); + Getters = ResolveGetHandlers(claimProps).ToArray(); + } + + private static string GetClaimType(MemberInfo property) + { + var claimType = property.Name; + var attr = property.CustomAttributes + .FirstOrDefault(x => x.AttributeType == typeof(ClaimTypeAttribute)); + if (attr != null) + { + claimType = attr.ConstructorArguments[0].Value as string ?? property.Name; + } + + return claimType; + } + + private static IEnumerable ResolveSetHandlers( + IEnumerable properties) => + from property in properties.Where(x => x.CanWrite) + select ResolveSetHandler(property); + + private static IEnumerable ResolveGetHandlers( + IEnumerable properties) => + from property in properties.Where(x => x.CanRead) + select ResolveGetHandler(property); + + private static GetProperty ResolveGetHandler(PropertyInfo propertyInfo) + { + var claimType = GetClaimType(propertyInfo); + var getter = propertyInfo.CreateGetter(); + var propertyType = propertyInfo.PropertyType; + if (propertyType == typeof(string) + || propertyType == typeof(object) + || propertyType.IsEnum + || CacheContext.StringConvertableSystemTypes.ContainsKey(propertyType)) + return new GetProperty(claimType, x => getter(x)!); + + if (CanBeIEnumerable(propertyType, out var elementType)) + { + var select = GetSelect(elementType!); + return new GetProperty(claimType, x => getter(x)!, x => select((IEnumerable)x)!); + } + + var serialize = Serialize(propertyType); + return new GetProperty(claimType, x => getter(x)!, x => Yield(serialize(x))); + } + private static SetProperty ResolveSetHandler(PropertyInfo propertyInfo) + { + var claimType = GetClaimType(propertyInfo); + var setter = propertyInfo.CreateSetter(); + var setterFun = (Action)((o, v) => setter(ref o!, v)); + var propertyType = propertyInfo.PropertyType; + if (propertyType == typeof(string) || propertyType == typeof(object)) + return new SetProperty(claimType, setterFun); + if (propertyType.IsEnum) + return new SetProperty(claimType, setterFun, s => Enum.Parse(propertyType, s.First())); + + if (CacheContext.StringConvertableSystemTypes.TryGetValue(propertyType, out var handler)) + return new SetProperty(claimType, setterFun, s => handler(s.First())); + if (CanBeIEnumerable(propertyType, out var elementType)) + { + var select = SetSelect(elementType!); + var to = MapTo(propertyType, elementType!); + return new SetProperty(claimType, setterFun, x => to(select(x))); + } + + var serialize = Deserialize(propertyType); + return new SetProperty(claimType, setterFun, x => serialize(x.First())); + } + + private static Func> GetSelect(Type elementType) + { + if (elementType == typeof(string) + || elementType == typeof(object) + || elementType.IsEnum + || CacheContext.StringConvertableSystemTypes.ContainsKey(elementType)) + return enumerable => + enumerable.Cast().Select(x => x?.ToString()); + + return enumerable => ((IEnumerable)enumerable).Select(Serialize(elementType)); + } + private static Func, IEnumerable> SetSelect(Type elementType) + { + if (elementType == typeof(string) || elementType == typeof(object)) + return enumerable => enumerable; + + if (elementType.IsEnum) + return enumerable => enumerable.Select(x => Enum.Parse(elementType, x)); + + if (CacheContext.StringConvertableSystemTypes.TryGetValue(elementType, out var handler)) + return enumerable => enumerable.Select(handler); + + return enumerable => enumerable.Select(Deserialize(elementType)); + } + + private static IEnumerable Yield(string o) + { + yield return o; + } + + private static Func Deserialize(Type valueType) => x => x.JsonDeserialize(valueType); + private static Func Serialize(Type valueType) => x => x.JsonSerialize(valueType); + + private static bool CanBeIEnumerable(Type type, out Type? elementType) + { + elementType = null; + if (type.IsArray) return true; + if (type.IsInterface) + { + if (Selector(type, out elementType)) + { + return true; + } + } + + foreach (var @interface in CacheContext.EnumAllInterfaces(type)) + { + if (Selector(@interface, out elementType)) + { + return true; + } + } + + return false; + + static bool Selector(Type t, out Type? elementType) + { + elementType = null; + if (!t.IsGenericType || t.GetGenericTypeDefinition() != typeof(IEnumerable<>)) return false; + elementType = t.GetGenericArguments()[0]; + return true; + } + } + + private static Func, object> MapToCollection(Type collectionType, Type elementType) + { + var add = CacheContext.Default.GetCollectionAdder(elementType); + var ctor = CacheContext.Default.GetCollectionConstructor(collectionType); + return x => + { + var set = ctor()!; + foreach (var o in x) + { + add(set, o); + } + + return set; + }; + } + private static Func, object> MapTo(Type containerType, Type elementType) + { + if (containerType.IsArray) // is T[] + { + var toArray = CacheContext.Default.GetToArrayHandler(elementType); + return x => toArray(null, x)!; + } + + if (containerType is { IsClass: true, IsGenericType: true }) + { + var genericTypeDefinition = containerType.GetGenericTypeDefinition(); + if (genericTypeDefinition == typeof(List<>)) // is List + { + var toList = CacheContext.Default.GetToListHandler(elementType); + return x => toList(null, x)!; + } + + if (CacheContext.SystemGenericCollectionTypes.Any(x => + x.MakeGenericType(elementType) == containerType)) // is System Generic Collection + { + return MapToCollection(containerType, elementType); + } + + // is custom collection + if (typeof(ICollection<>).MakeGenericType(elementType).IsAssignableFrom(containerType)) + { + return MapToCollection(containerType, elementType); + } + } + + if (containerType.IsInterface) + { + if (containerType.IsAssignableFrom(typeof(List<>).MakeGenericType(elementType))) // can be List + { + var toList = CacheContext.Default.GetToListHandler(elementType); + return x => toList(null, x)!; + } + + var type = CacheContext.SystemGenericCollectionTypes + .Select(x => x.MakeGenericType(elementType)) + .FirstOrDefault(containerType.IsAssignableFrom); + + if (type != null) + { + return MapToCollection(type, elementType); + } + } + + throw new ArgumentException("Not supported type."); + } + + +} \ No newline at end of file diff --git a/src/Antelcat.ClaimSerialization/Metadata/SetProperty.cs b/src/Antelcat.ClaimSerialization/Metadata/SetProperty.cs new file mode 100644 index 0000000..74e6373 --- /dev/null +++ b/src/Antelcat.ClaimSerialization/Metadata/SetProperty.cs @@ -0,0 +1,20 @@ +namespace Antelcat.ClaimSerialization.Metadata; + +public class SetProperty(string type, Action setter) +{ + public string Type { get; } = type; + + public SetProperty(string type, + Action setter, + Func, object?> dynamicTransform) : + this(type, setter) => Transform = dynamicTransform; + + private Action Handler { get; } = setter; + private Func, object?> Transform { get; } = s => s.First(); + + public void Set(object target, IEnumerable value) + { + var val = Transform(value); + if (val != null) Handler(target, val); + } +} \ No newline at end of file