< Summary

Information
Class: Elsa.Common.Serialization.SerializationTypeResolver
Assembly: Elsa.Common
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Common/Serialization/SerializationTypeResolver.cs
Line coverage
54%
Covered lines: 88
Uncovered lines: 74
Coverable lines: 162
Total lines: 318
Line coverage: 54.3%
Branch coverage
41%
Covered branches: 38
Total branches: 92
Branch coverage: 41.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Common/Serialization/SerializationTypeResolver.cs

#LineLine coverage
 1using System.Collections;
 2using System.Collections.ObjectModel;
 3using System.Reflection;
 4using System.Text.Json;
 5using Elsa.Extensions;
 6
 7namespace Elsa.Common.Serialization;
 8
 9/// <summary>
 10/// Resolves serialization type aliases without loading arbitrary CLR type names.
 11/// </summary>
 12public static class SerializationTypeResolver
 13{
 114    private static readonly IDictionary<string, Type> GenericCollectionTypes = new Dictionary<string, Type>(StringCompar
 115    {
 116        ["IEnumerable"] = typeof(IEnumerable<>),
 117        ["ICollection"] = typeof(ICollection<>),
 118        ["IList"] = typeof(IList<>),
 119        ["IReadOnlyCollection"] = typeof(IReadOnlyCollection<>),
 120        ["IReadOnlyList"] = typeof(IReadOnlyList<>),
 121        ["ISet"] = typeof(ISet<>),
 122        ["List"] = typeof(List<>),
 123        ["HashSet"] = typeof(HashSet<>),
 124        ["Collection"] = typeof(Collection<>)
 125    };
 26
 127    private static readonly IDictionary<Type, string> GenericCollectionAliases = new Dictionary<Type, string>
 128    {
 129        [typeof(List<>)] = "List",
 130        [typeof(HashSet<>)] = "HashSet",
 131        [typeof(Collection<>)] = "Collection"
 132    };
 33
 134    private static readonly IDictionary<Type, Type> GenericCollectionInterfaceMappings = new Dictionary<Type, Type>
 135    {
 136        [typeof(IEnumerable<>)] = typeof(List<>),
 137        [typeof(ICollection<>)] = typeof(List<>),
 138        [typeof(IList<>)] = typeof(List<>),
 139        [typeof(IReadOnlyCollection<>)] = typeof(List<>),
 140        [typeof(IReadOnlyList<>)] = typeof(List<>),
 141        [typeof(ISet<>)] = typeof(HashSet<>),
 142        [typeof(IDictionary<,>)] = typeof(Dictionary<,>),
 143        [typeof(IReadOnlyDictionary<,>)] = typeof(Dictionary<,>)
 144    };
 45
 146    private static readonly IDictionary<Type, Type> CollectionInterfaceMappings = new Dictionary<Type, Type>
 147    {
 148        [typeof(IEnumerable)] = typeof(List<object>),
 149        [typeof(ICollection)] = typeof(List<object>),
 150        [typeof(IList)] = typeof(List<object>),
 151        [typeof(IDictionary)] = typeof(Dictionary<string, object>)
 152    };
 53
 54    /// <summary>
 55    /// Resolves the specified serialization type alias.
 56    /// </summary>
 57    public static Type ResolveType(ISerializationTypeRegistry serializationTypeRegistry, string? typeAlias)
 58    {
 493859        if (string.IsNullOrWhiteSpace(typeAlias))
 060            throw new JsonException("The serialization type alias is missing.");
 61
 493862        if (TryResolveType(serializationTypeRegistry, typeAlias, out var type))
 493863            return type;
 64
 065        throw new JsonException(
 066            $"Unknown serialization type alias '{typeAlias}'. Only registered aliases and supported compound aliases can
 67    }
 68
 69    /// <summary>
 70    /// Attempts to resolve the specified serialization type alias.
 71    /// </summary>
 72    public static bool TryResolveType(ISerializationTypeRegistry serializationTypeRegistry, string typeAlias, out Type t
 73    {
 1001674        IReadOnlyList<Type>? registeredTypes = null;
 1001675        return TryResolveType(serializationTypeRegistry, typeAlias, ref registeredTypes, out type);
 76    }
 77
 78    private static bool TryResolveType(ISerializationTypeRegistry serializationTypeRegistry, string typeAlias, ref IRead
 79    {
 1015180        if (serializationTypeRegistry.TryGetType(typeAlias, out var registeredType))
 81        {
 1001682            type = registeredType;
 1001683            return true;
 84        }
 85
 13586        if (TryResolveArrayType(serializationTypeRegistry, typeAlias, ref registeredTypes, out var arrayType))
 87        {
 12288            type = arrayType;
 12289            return true;
 90        }
 91
 1392        if (TryResolveGenericCollectionType(serializationTypeRegistry, typeAlias, ref registeredTypes, out var genericCo
 93        {
 1394            type = genericCollectionType;
 1395            return true;
 96        }
 97
 098        if (TryResolveRegisteredLegacyTypeName(serializationTypeRegistry, typeAlias, ref registeredTypes, out var legacy
 99        {
 0100            type = legacyType;
 0101            return true;
 102        }
 103
 0104        type = null!;
 0105        return false;
 106    }
 107
 108    /// <summary>
 109    /// Attempts to return a serialization type alias that this resolver can read back.
 110    /// </summary>
 111    public static bool TryGetAlias(ISerializationTypeRegistry serializationTypeRegistry, Type type, out string alias)
 112    {
 28043113        if (serializationTypeRegistry.TryGetAlias(type, out alias!))
 23888114            return true;
 115
 4155116        if (type.IsArray)
 117        {
 63118            var elementType = type.GetElementType()!;
 119
 63120            if (TryGetAlias(serializationTypeRegistry, elementType, out var elementTypeAlias))
 121            {
 63122                alias = $"{elementTypeAlias}[]";
 63123                return true;
 124            }
 125        }
 126
 4092127        if (type is { IsGenericType: true, GenericTypeArguments.Length: 1 })
 128        {
 1295129            var genericTypeDefinition = type.GetGenericTypeDefinition();
 130
 1295131            if (TryGetWritableGenericCollectionAlias(genericTypeDefinition, out var genericTypeAlias) &&
 1295132                TryGetAlias(serializationTypeRegistry, type.GenericTypeArguments[0], out var elementTypeAlias))
 133            {
 1291134                alias = $"{genericTypeAlias}<{elementTypeAlias}>";
 1291135                return true;
 136            }
 137        }
 138
 2801139        alias = null!;
 2801140        return false;
 141    }
 142
 143    /// <summary>
 144    /// Attempts to map a supported collection interface type to an instantiable concrete type.
 145    /// </summary>
 146    public static bool TryGetInstantiableCollectionType(Type type, out Type instantiableType)
 147    {
 0148        if (type.IsGenericType)
 149        {
 0150            var genericTypeDefinition = type.GetGenericTypeDefinition();
 0151            if (GenericCollectionInterfaceMappings.TryGetValue(genericTypeDefinition, out var instantiableGenericTypeDef
 152            {
 0153                instantiableType = instantiableGenericTypeDefinition.MakeGenericType(type.GenericTypeArguments);
 0154                return true;
 155            }
 156        }
 157
 0158        if (CollectionInterfaceMappings.TryGetValue(type, out instantiableType!))
 0159            return true;
 160
 0161        instantiableType = null!;
 0162        return false;
 163    }
 164
 165    private static bool TryResolveArrayType(ISerializationTypeRegistry serializationTypeRegistry, string typeAlias, ref 
 166    {
 135167        type = null!;
 168
 135169        if (!typeAlias.EndsWith("[]", StringComparison.Ordinal))
 13170            return false;
 171
 122172        var elementTypeAlias = typeAlias[..^2];
 122173        if (!TryResolveType(serializationTypeRegistry, elementTypeAlias, ref registeredTypes, out var elementType))
 0174            return false;
 175
 122176        type = elementType.MakeArrayType();
 122177        return true;
 178    }
 179
 180    private static bool TryResolveGenericCollectionType(ISerializationTypeRegistry serializationTypeRegistry, string typ
 181    {
 13182        type = null!;
 13183        var genericStart = typeAlias.IndexOf('<', StringComparison.Ordinal);
 184
 13185        if (genericStart <= 0 || !typeAlias.EndsWith(">", StringComparison.Ordinal))
 0186            return false;
 187
 13188        var genericTypeAlias = typeAlias[..genericStart];
 13189        if (!GenericCollectionTypes.TryGetValue(genericTypeAlias, out var genericTypeDefinition))
 0190            return false;
 191
 13192        var elementTypeAlias = typeAlias[(genericStart + 1)..^1];
 13193        if (!TryResolveType(serializationTypeRegistry, elementTypeAlias, ref registeredTypes, out var elementType))
 0194            return false;
 195
 13196        type = genericTypeDefinition.MakeGenericType(elementType);
 13197        return true;
 198    }
 199
 200    private static bool TryGetWritableGenericCollectionAlias(Type genericTypeDefinition, out string alias)
 201    {
 1295202        if (GenericCollectionAliases.TryGetValue(genericTypeDefinition, out alias!))
 637203            return true;
 204
 658205        if (GenericCollectionInterfaceMappings.TryGetValue(genericTypeDefinition, out var instantiableGenericTypeDefinit
 658206            GenericCollectionAliases.TryGetValue(instantiableGenericTypeDefinition, out alias!))
 207        {
 654208            return true;
 209        }
 210
 4211        alias = null!;
 4212        return false;
 213    }
 214
 215    private static bool TryResolveRegisteredLegacyTypeName(ISerializationTypeRegistry serializationTypeRegistry, string 
 216    {
 0217        registeredTypes ??= GetRegisteredTypes(serializationTypeRegistry);
 0218        var registeredTypeSnapshot = registeredTypes;
 219
 0220        if (TryResolveRegisteredSimpleAssemblyQualifiedName(registeredTypeSnapshot, typeAlias, out type))
 0221            return true;
 222
 0223        if (TryResolveLegacyGenericCollectionTypeName(serializationTypeRegistry, typeAlias, ref registeredTypes, out typ
 0224            return true;
 225
 226        Type? resolvedType;
 227
 228        try
 229        {
 0230            resolvedType = Type.GetType(
 0231                typeAlias,
 0232                assemblyName => ResolveAssembly(registeredTypeSnapshot, assemblyName),
 0233                (assembly, typeName, ignoreCase) => ResolveType(registeredTypeSnapshot, assembly, typeName, ignoreCase),
 0234                false);
 0235        }
 0236        catch (Exception e) when (e is ArgumentException or FileLoadException)
 237        {
 0238            resolvedType = null;
 0239        }
 240
 0241        type = resolvedType!;
 0242        return resolvedType != null;
 243    }
 244
 245    private static IReadOnlyList<Type> GetRegisteredTypes(ISerializationTypeRegistry serializationTypeRegistry)
 246    {
 0247        return serializationTypeRegistry.ListTypes().ToArray();
 248    }
 249
 250    private static bool TryResolveRegisteredSimpleAssemblyQualifiedName(IEnumerable<Type> registeredTypes, string typeAl
 251    {
 0252        type = registeredTypes.FirstOrDefault(x =>
 0253            string.Equals(x.GetSimpleAssemblyQualifiedName(), typeAlias, StringComparison.Ordinal) ||
 0254            string.Equals(x.AssemblyQualifiedName, typeAlias, StringComparison.Ordinal))!;
 255
 0256        return type != null;
 257    }
 258
 259    private static bool TryResolveLegacyGenericCollectionTypeName(ISerializationTypeRegistry serializationTypeRegistry, 
 260    {
 0261        type = null!;
 262
 0263        foreach (var genericTypeDefinition in GenericCollectionTypes.Values)
 264        {
 0265            var prefix = $"{genericTypeDefinition.FullName}[[";
 0266            var separatorIndex = typeAlias.LastIndexOf("]], ", StringComparison.Ordinal);
 267
 0268            if (!typeAlias.StartsWith(prefix, StringComparison.Ordinal) || separatorIndex <= prefix.Length)
 269                continue;
 270
 0271            var assemblyName = typeAlias[(separatorIndex + 4)..].Split(',')[0];
 0272            if (!string.Equals(assemblyName, genericTypeDefinition.Assembly.GetName().Name, StringComparison.Ordinal))
 273                continue;
 274
 0275            var elementTypeAlias = typeAlias[prefix.Length..separatorIndex];
 0276            if (!TryResolveType(serializationTypeRegistry, elementTypeAlias, ref registeredTypes, out var elementType))
 0277                return false;
 278
 279            // The resolver only closes known collection definitions over registered element types.
 280#pragma warning disable IL2055
 0281            type = genericTypeDefinition.MakeGenericType(elementType);
 282#pragma warning restore IL2055
 0283            return true;
 284        }
 285
 0286        return false;
 0287    }
 288
 289    private static Assembly? ResolveAssembly(IEnumerable<Type> registeredTypes, AssemblyName assemblyName)
 290    {
 0291        var coreLibAssembly = typeof(List<>).Assembly;
 0292        if (AssemblyName.ReferenceMatchesDefinition(coreLibAssembly.GetName(), assemblyName))
 0293            return coreLibAssembly;
 294
 0295        return registeredTypes
 0296            .Select(x => x.Assembly)
 0297            .Distinct()
 0298            .FirstOrDefault(x => AssemblyName.ReferenceMatchesDefinition(x.GetName(), assemblyName));
 299    }
 300
 301    private static Type? ResolveType(IEnumerable<Type> registeredTypes, Assembly? assembly, string typeName, bool ignore
 302    {
 0303        if (assembly == typeof(List<>).Assembly)
 304        {
 0305            var genericCollectionType = GenericCollectionTypes.Values.FirstOrDefault(x =>
 0306                x.FullName != null && string.Equals(x.FullName, typeName, ignoreCase ? StringComparison.OrdinalIgnoreCas
 307
 0308            if (genericCollectionType != null)
 0309                return genericCollectionType;
 310        }
 311
 0312        return registeredTypes.FirstOrDefault(x =>
 0313            x.Assembly == assembly &&
 0314            x.FullName != null &&
 0315            (string.Equals(x.FullName, typeName, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordi
 0316             string.Equals(x.FullName.Replace('+', '.'), typeName, ignoreCase ? StringComparison.OrdinalIgnoreCase : Str
 317    }
 318}

Methods/Properties

.cctor()
ResolveType(Elsa.Common.Serialization.ISerializationTypeRegistry,System.String)
TryResolveType(Elsa.Common.Serialization.ISerializationTypeRegistry,System.String,System.Type&)
TryResolveType(Elsa.Common.Serialization.ISerializationTypeRegistry,System.String,System.Collections.Generic.IReadOnlyList`1<System.Type>&,System.Type&)
TryGetAlias(Elsa.Common.Serialization.ISerializationTypeRegistry,System.Type,System.String&)
TryGetInstantiableCollectionType(System.Type,System.Type&)
TryResolveArrayType(Elsa.Common.Serialization.ISerializationTypeRegistry,System.String,System.Collections.Generic.IReadOnlyList`1<System.Type>&,System.Type&)
TryResolveGenericCollectionType(Elsa.Common.Serialization.ISerializationTypeRegistry,System.String,System.Collections.Generic.IReadOnlyList`1<System.Type>&,System.Type&)
TryGetWritableGenericCollectionAlias(System.Type,System.String&)
TryResolveRegisteredLegacyTypeName(Elsa.Common.Serialization.ISerializationTypeRegistry,System.String,System.Collections.Generic.IReadOnlyList`1<System.Type>&,System.Type&)
GetRegisteredTypes(Elsa.Common.Serialization.ISerializationTypeRegistry)
TryResolveRegisteredSimpleAssemblyQualifiedName(System.Collections.Generic.IEnumerable`1<System.Type>,System.String,System.Type&)
TryResolveLegacyGenericCollectionTypeName(Elsa.Common.Serialization.ISerializationTypeRegistry,System.String,System.Collections.Generic.IReadOnlyList`1<System.Type>&,System.Type&)
ResolveAssembly(System.Collections.Generic.IEnumerable`1<System.Type>,System.Reflection.AssemblyName)
ResolveType(System.Collections.Generic.IEnumerable`1<System.Type>,System.Reflection.Assembly,System.String,System.Boolean)