• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LBreedlove / Queuebal.Expressions / 15960686453

29 Jun 2025 11:50PM UTC coverage: 96.437% (-0.06%) from 96.497%
15960686453

Pull #15

github

web-flow
Merge b8a08fc0c into 737aef052
Pull Request #15: Remove Core, and Services, Add nuspecs

383 of 387 branches covered (98.97%)

Branch coverage included in aggregate %.

4 of 6 new or added lines in 1 file covered. (66.67%)

2405 of 2504 relevant lines covered (96.05%)

473.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.78
/Queuebal.Serialization/TypeRegistryService.cs
1
using System.Reflection;
2

3
namespace Queuebal.Serialization;
4

5

6
public interface ITypeRegistryService
7
{
8
    /// <summary>
9
    /// Gets the base type for the type registry.
10
    /// </summary>
11
    Type BaseType { get; }
12

13
    /// <summary>
14
    /// Gets the field name used to discriminate between different types in the type map.
15
    /// </summary>
16
    string DiscriminatorField { get; }
17

18
    /// <summary>
19
    /// Gets the registered type mappings.
20
    /// </summary>
21
    Dictionary<string, Type> TypeMap { get; }
22

23
    /// <summary>
24
    /// Registers a type name with its corresponding type.
25
    /// </summary>
26
    ITypeRegistryService WithTypeMapping(Type implementationType);
27

28
    /// <summary>
29
    /// Registers a type name with its corresponding type.
30
    /// </summary>
31
    ITypeRegistryService WithTypeMapping<TImplementationType>();
32
}
33

34

35
public class TypeRegistryService<TBaseType> : ITypeRegistryService where TBaseType : class
36
{
37
    /// <summary>
38
    /// The object used to synchronize access to the type map.
39
    /// </summary>
40
    private readonly object _syncLock = new();
24✔
41

42
    /// <summary>
43
    /// A map of type names, as used in the discriminator field, to their type.
44
    /// </summary>
45
    private readonly Dictionary<string, Type> _typeMap = new(StringComparer.OrdinalIgnoreCase);
24✔
46

47
    /// <summary>
48
    /// The field name used to discriminate between different types in the type map.
49
    /// </summary>
50
    private readonly string _discriminatorField;
51

52
    /// <summary>
53
    /// Gets the field name used to discriminate between different types in the type map.
54
    /// </summary>
55
    public string DiscriminatorField => _discriminatorField;
17✔
56

57
    /// <summary>
58
    /// Gets the BaseType for the registry.
59
    /// </summary>
60
    public Type BaseType => typeof(TBaseType);
14✔
61

62
    /// <summary>
63
    /// Gets the registered type mappings.
64
    /// </summary>
65
    public Dictionary<string, Type> TypeMap
66
    {
67
        get
68
        {
19✔
69
            lock (_syncLock)
19✔
70
            {
19✔
71
                return _typeMap.ToDictionary
19✔
72
                (
19✔
73
                    kvp => kvp.Key,
226✔
74
                    kvp => kvp.Value,
226✔
75
                    StringComparer.OrdinalIgnoreCase
19✔
76
                );
19✔
77
            }
78
        }
19✔
79
    }
80

81
    /// <summary>
82
    /// Initializes a new instance of the <see cref="TypeRegistryService{TBaseType}"/> class.
83
    /// </summary>
84
    /// <param name="discriminatorField">The name of the property used as the discriminator value.</param>
85
    /// <exception cref="ArgumentException">Thrown if the discriminatorField is null or whitespace.</exception>
86
    public TypeRegistryService(string discriminatorField = "Type")
24✔
87
    {
24✔
88
        if (string.IsNullOrWhiteSpace(discriminatorField))
24✔
89
        {
1✔
90
            throw new ArgumentException("Discriminator field cannot be null or empty.", nameof(discriminatorField));
1✔
91
        }
92

93
        var baseType = typeof(TBaseType);
23✔
94
        var discriminatorProperty = baseType.GetProperty(discriminatorField, BindingFlags.Public | BindingFlags.Static);
23✔
95
        if (discriminatorProperty == null || discriminatorProperty.PropertyType != typeof(string))
23✔
96
        {
1✔
97
            throw new InvalidOperationException($"The base type '{baseType.FullName}' does not have a valid public static '{discriminatorField}' property.");
1✔
98
        }
99

100
        _discriminatorField = discriminatorField;
22✔
101
    }
22✔
102

103
    /// <summary>
104
    /// Registers a type name with its corresponding type.
105
    /// </summary>
106
    /// <param name="implementationType">The type used as the implementation.</param>
107
    /// <returns
108
    /// The <see cref="TypeRegistryService"/> instance for method chaining.</returns>
109
    /// </summary>
110
    public ITypeRegistryService WithTypeMapping<TImplementationType>()
111
    {
1✔
112
        RegisterTypeMapping(typeof(TImplementationType));
1✔
113
        return this;
1✔
114
    }
1✔
115

116
    /// <summary>
117
    /// Registers a type name with its corresponding type.
118
    /// </summary>
119
    /// <param name="implementationType">The type used as the implementation.</param>
120
    /// <returns
121
    /// The <see cref="TypeRegistryService"/> instance for method chaining.</returns>
122
    /// </summary>
123
    public ITypeRegistryService WithTypeMapping(Type implementationType)
124
    {
1✔
125
        RegisterTypeMapping(implementationType);
1✔
126
        return this;
1✔
127
    }
1✔
128

129
    /// <summary>
130
    /// Registers a type with its discriminator value.
131
    /// </summary>
132
    /// <param name="implementationType">The type of the implementing class.</param>
133
    public void RegisterTypeMapping(Type implementationType)
134
    {
214✔
135
        lock (_syncLock)
214✔
136
        {
214✔
137
            if (!typeof(TBaseType).IsAssignableFrom(implementationType))
214✔
138
            {
1✔
139
                throw new ArgumentException($"Type '{implementationType.FullName}' does not implement {typeof(TBaseType).FullName}.", nameof(implementationType));
1✔
140
            }
141

142
            var discriminatorTypeProperty = implementationType.GetProperty(_discriminatorField, BindingFlags.Public | BindingFlags.Static);
213✔
143
            if (discriminatorTypeProperty != null && discriminatorTypeProperty.PropertyType == typeof(string))
213✔
144
            {
212✔
145
                // Register the type with its discriminator value
146
                var discriminatorValue = (string)discriminatorTypeProperty.GetValue(null)!;
212✔
147
                if (string.IsNullOrWhiteSpace(discriminatorValue))
212✔
148
                {
1✔
149
                    throw new ArgumentException($"The discriminator value for type '{implementationType.FullName}' cannot be null or empty.", nameof(discriminatorValue));
1✔
150
                }
151

152
                if (_typeMap.ContainsKey(discriminatorValue))
211✔
153
                {
1✔
154
                    throw new InvalidOperationException($"A type with the name '{discriminatorValue}' is already registered.");
1✔
155
                }
156

157
                _typeMap[discriminatorValue] = implementationType;
210✔
158
                return;
210✔
159
            }
160

161
            throw new InvalidOperationException($"Type '{implementationType.FullName}' does not have a valid public static '{_discriminatorField}' property.");
1✔
162
        }
163
    }
210✔
164

165
    /// <summary>
166
    /// Builds an TypeRegistryService using all the loaded assemblies in the current AppDomain.
167
    /// </summary>
168
    /// <returns>
169
    /// A new TypeRegistryService with the TBaseType types in the assemblies in the current
170
    /// AppDomain registered.
171
    /// </returns>
172
    public static TypeRegistryService<TBaseType> BuildFromCurrentAppDomain(string discriminatorField)
173
    {
16✔
174
        // Get all types in the loaded assemblies in the current AppDomain
175
        var assemblies = AppDomain.CurrentDomain.GetAssemblies();
16✔
176
        return BuildFromAssemblies(assemblies, discriminatorField);
16✔
177
    }
16✔
178

179
    /// <summary>
180
    /// Builds an TypeRegistryService using the provided assemblies.
181
    /// </summary>
182
    /// <returns>
183
    /// A new TypeRegistryService with the TBaseType types in the provided assemblies
184
    /// registered.
185
    /// </returns>
186
    public static TypeRegistryService<TBaseType> BuildFromAssemblies(IEnumerable<Assembly> assemblies, string discriminatorField)
187
    {
16✔
188
        var service = new TypeRegistryService<TBaseType>(discriminatorField);
16✔
189

190
        foreach (var assembly in assemblies)
2,536✔
191
        {
1,244✔
192
            if (assembly is null)
1,244✔
NEW
193
            {
×
194
                // the compiler thinks assembly can be null for some reason...
NEW
195
                continue;
×
196
            }
197

198
            if (assembly.FullName?.StartsWith("Queuebal.UnitTests") ?? false)
1,244✔
199
            {
16✔
200
                // we have an InvalidExpression class that will attempt to
201
                // register that needs to be skipped
202
                continue;
16✔
203
            }
204

205
            foreach (var type in assembly.GetTypes())
382,526✔
206
            {
189,421✔
207
                if (type.IsAssignableTo(typeof(TBaseType)) && !type.IsAbstract)
189,421✔
208
                {
207✔
209
                    service.RegisterTypeMapping(type);
207✔
210
                }
207✔
211
            }
189,421✔
212
        }
1,228✔
213

214
        return service;
16✔
215
    }
16✔
216
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc