Skip to content

Commit 183c8f2

Browse files
author
Mykyta Zotov
committed
Add shared node types for Enum, Guid, Float, Double, Decimal, and related tests
1 parent 67dd58b commit 183c8f2

File tree

68 files changed

+3066
-1848
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+3066
-1848
lines changed

src/StringEnricher/Configuration/BufferAllocationThresholds.cs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
/// Settings related to buffer sizes for string operations.
55
/// Use this struct to configure and represent buffer size settings for different extensions.
66
/// </summary>
7-
internal struct BufferAllocationThresholds
7+
public struct BufferAllocationThresholds
88
{
99
private readonly string _name;
1010

@@ -59,8 +59,10 @@ internal BufferAllocationThresholds(string name)
5959
/// </param>
6060
internal BufferAllocationThresholds(string name, int maxStackAllocLength, int maxPooledArrayLength) : this(name)
6161
{
62-
MaxStackAllocLength = maxStackAllocLength;
63-
MaxPooledArrayLength = maxPooledArrayLength;
62+
_maxStackAllocLength = maxStackAllocLength;
63+
_maxPooledArrayLength = maxPooledArrayLength;
64+
ValidateMaxStackAllocLengthNewValue(_maxStackAllocLength);
65+
ValidateMaxPooledArrayLengthNewValue(_maxPooledArrayLength);
6466
}
6567

6668
#region MaxStackAllocLength
@@ -103,13 +105,14 @@ private void ValidateMaxStackAllocLengthNewValue(int value)
103105
{
104106
// strict validation to prevent misconfiguration
105107

106-
if (value <= 0)
108+
// zero is allowed here to effectively disable stack allocation
109+
if (value < 0)
107110
{
108111
// must be positive
109112
throw new ArgumentOutOfRangeException(
110113
nameof(value),
111114
value,
112-
$"{nameof(MaxStackAllocLength)} must be greater than zero.");
115+
$"{nameof(MaxStackAllocLength)} must be positive.");
113116
}
114117

115118
const int hardLimit = 2048; // Arbitrary hard limit to prevent excessive stack usage.
@@ -193,13 +196,14 @@ private void ValidateMaxPooledArrayLengthNewValue(int value)
193196
{
194197
// strict validation to prevent misconfiguration
195198

196-
if (value <= 0)
199+
// zero is allowed here to effectively disable pooling
200+
if (value < 0)
197201
{
198202
// must be positive
199203
throw new ArgumentOutOfRangeException(
200204
nameof(value),
201205
value,
202-
$"{nameof(MaxPooledArrayLength)} must be greater than zero.");
206+
$"{nameof(MaxPooledArrayLength)} must be positive.");
203207
}
204208

205209
if (value < _maxStackAllocLength)

src/StringEnricher/Configuration/BufferSizes.cs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
/// <summary>
44
/// Configuration for buffer sizes used in various operations.
55
/// </summary>
6-
internal struct BufferSizes
6+
public struct BufferSizes
77
{
88
private readonly string _name;
99

@@ -19,7 +19,7 @@ internal BufferSizes(string name)
1919
_name = name;
2020
_initialBufferLength = 1;
2121
_maxBufferLength = 1_000_000;
22-
GrowthFactor = 2;
22+
_growthFactor = 2;
2323
}
2424

2525
/// <summary>
@@ -40,8 +40,10 @@ internal BufferSizes(string name)
4040
/// </param>
4141
internal BufferSizes(string name, int initialBufferLength, int maxBufferLength) : this(name)
4242
{
43-
InitialBufferLength = initialBufferLength;
44-
MaxBufferLength = maxBufferLength;
43+
_initialBufferLength = initialBufferLength;
44+
_maxBufferLength = maxBufferLength;
45+
ValidateInitialBufferLengthNewValue(_initialBufferLength);
46+
ValidateMaxBufferLengthNewValue(_maxBufferLength);
4547
}
4648

4749
/// <summary>
@@ -68,7 +70,8 @@ internal BufferSizes(string name, int initialBufferLength, int maxBufferLength)
6870
internal BufferSizes(string name, int initialBufferLength, int maxBufferLength, float growthFactor)
6971
: this(name, initialBufferLength, maxBufferLength)
7072
{
71-
GrowthFactor = growthFactor;
73+
_growthFactor = growthFactor;
74+
ValidateGrowthFactorNewValue(_growthFactor);
7275
}
7376

7477
#region GrowthFactor
@@ -91,7 +94,7 @@ internal float GrowthFactor
9194
}
9295
}
9396

94-
private static void ValidateGrowthFactorNewValue(float value)
97+
private void ValidateGrowthFactorNewValue(float value)
9598
{
9699
if (value <= 1.0f)
97100
{
@@ -101,6 +104,15 @@ private static void ValidateGrowthFactorNewValue(float value)
101104
$"{nameof(GrowthFactor)} must be greater than 1.0.");
102105
}
103106

107+
if ((int)(_initialBufferLength * value) == _initialBufferLength)
108+
{
109+
throw new ArgumentOutOfRangeException(
110+
nameof(value),
111+
value,
112+
$"{nameof(GrowthFactor)} is too small to cause any increase in buffer size from the current {nameof(InitialBufferLength)} ({_initialBufferLength}). " +
113+
$"Consider setting it to at least {(float)(_initialBufferLength + 1) / _initialBufferLength}.");
114+
}
115+
104116
if (value > 10.0f && StringEnricherSettings.EnableDebugLogs)
105117
{
106118
// warn about very high values
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
using StringEnricher.Nodes.Shared;
2+
3+
namespace StringEnricher.Configuration;
4+
5+
/// <summary>
6+
/// Configuration settings for a node.
7+
/// </summary>
8+
public struct NodeSettings
9+
{
10+
/// <summary>
11+
/// The name of the node.
12+
/// </summary>
13+
public string Name { get; }
14+
15+
/// <summary>
16+
/// The buffer sizes to use for the node.
17+
/// </summary>
18+
private BufferSizes _bufferSizes;
19+
20+
/// <summary>
21+
/// The buffer allocation thresholds to use for the node.
22+
/// </summary>
23+
private BufferAllocationThresholds _bufferAllocationThresholds;
24+
25+
/// <summary>
26+
/// Initializes a new instance of the <see cref="NodeSettings"/> struct.
27+
/// </summary>
28+
public NodeSettings(
29+
string name,
30+
BufferSizes bufferSizes,
31+
BufferAllocationThresholds bufferAllocationThresholds
32+
)
33+
{
34+
Name = name;
35+
_bufferSizes = bufferSizes;
36+
_bufferAllocationThresholds = bufferAllocationThresholds;
37+
}
38+
39+
/// <summary>
40+
/// Initializes a new instance of the <see cref="NodeSettings"/> struct.
41+
/// </summary>
42+
/// <param name="name">The name of the node.</param>
43+
/// <param name="initialBufferLength">
44+
/// The initial length of the buffer to be used for memory allocation.
45+
/// This value must be greater than zero and less than or equal to <paramref name="maxBufferLength"/>.
46+
/// </param>
47+
/// <param name="maxBufferLength">
48+
/// The maximum length of the buffer to be used for memory allocation when the initial buffer is insufficient.
49+
/// This value must be greater than zero and greater than or equal to <paramref name="initialBufferLength"/>.
50+
/// </param>
51+
/// <param name="growthFactor">
52+
/// The growth factor to use when increasing buffer sizes.
53+
/// This factor determines how quickly the buffer size increases when the initial buffer is insufficient.
54+
/// Must be greater than 1.0.
55+
/// </param>
56+
/// <param name="maxStackAllocLength">
57+
/// The maximum length of the buffer to be allocated on the stack.
58+
/// If a buffer larger than this is needed, memory won't be allocated on the stack.
59+
/// If a buffer smaller than or equal to this is needed, it will be allocated on the stack.
60+
///
61+
/// Note: Dual-threshold memory model: two adjustable boundaries define how objects are allocated — to the stack,
62+
/// array pool, or heap. Shifting the thresholds dynamically redistributes memory ranges between these regions
63+
/// for optimal performance and balance.
64+
///
65+
/// |-----------|----------------------|--------------------------->
66+
/// ^ ^ ^
67+
/// 0 MaxStackAllocLength MaxPooledArrayLength
68+
///
69+
/// [0, MaxStackAllocLength) -> Stack allocation
70+
/// [MaxStackAllocLength, MaxPooledArrayLength) -> Array Pool
71+
/// [MaxPooledArrayLength, ∞) -> Heap allocation
72+
/// </param>
73+
/// <param name="maxPooledArrayLength">
74+
/// The maximum length of the buffer to be allocated from the array pool.
75+
/// If a buffer larger than this is needed, a new array will be allocated instead of renting from the pool.
76+
/// If a buffer smaller than or equal to this is needed, it will be rented from the pool.
77+
///
78+
/// Note: Dual-threshold memory model: two adjustable boundaries define how objects are allocated — to the stack,
79+
/// array pool, or heap. Shifting the thresholds dynamically redistributes memory ranges between these regions
80+
/// for optimal performance and balance.
81+
///
82+
/// |-----------|----------------------|--------------------------->
83+
/// ^ ^ ^
84+
/// 0 MaxStackAllocLength MaxPooledArrayLength
85+
///
86+
/// [0, MaxStackAllocLength) -> Stack allocation
87+
/// [MaxStackAllocLength, MaxPooledArrayLength) -> Array Pool
88+
/// [MaxPooledArrayLength, ∞) -> Heap allocation
89+
/// </param>
90+
public NodeSettings(
91+
string name,
92+
int initialBufferLength,
93+
int maxBufferLength,
94+
float growthFactor,
95+
int maxStackAllocLength,
96+
int maxPooledArrayLength
97+
) : this(
98+
name,
99+
new BufferSizes(name, initialBufferLength, maxBufferLength, growthFactor),
100+
new BufferAllocationThresholds(name, maxStackAllocLength, maxPooledArrayLength)
101+
)
102+
{
103+
}
104+
105+
/// <summary>
106+
/// The multiplier to use when resizing the buffer for formatting a <see cref="DateOnlyNode"/>.
107+
/// When the current buffer is insufficient, it will be resized by multiplying its size by this factor.
108+
/// Default is 2.0 (doubling the buffer size).
109+
/// Note: A higher multiplier reduces the number of resizing operations but may lead to increased memory usage.
110+
/// A lower multiplier minimizes memory usage but may increase the number of resizing operations, impacting performance.
111+
/// Recommended range is between 1.5 and 3.0.
112+
/// </summary>
113+
public float GrowthFactor
114+
{
115+
get => _bufferSizes.GrowthFactor;
116+
set => _bufferSizes.GrowthFactor = value;
117+
}
118+
119+
/// <summary>
120+
/// The initial buffer length to use when formatting a <see cref="DateOnlyNode"/>.
121+
/// This buffer is used to attempt formatting the node before falling back to larger buffers.
122+
/// </summary>
123+
public int InitialBufferSize
124+
{
125+
get => _bufferSizes.InitialBufferLength;
126+
set => _bufferSizes.InitialBufferLength = value;
127+
}
128+
129+
/// <summary>
130+
/// The maximum buffer length to use when formatting a <see cref="DateOnlyNode"/>.
131+
/// If formatting requires a buffer larger than this, an exception will be thrown.
132+
/// </summary>
133+
public int MaxBufferSize
134+
{
135+
get => _bufferSizes.MaxBufferLength;
136+
set => _bufferSizes.MaxBufferLength = value;
137+
}
138+
139+
/// <summary>
140+
/// The maximum length of a buffer that can be allocated on the stack when formatting a <see cref="DateOnlyNode"/>.
141+
/// Buffers larger than this will be allocated on the heap.
142+
/// </summary>
143+
public int MaxStackAllocLength
144+
{
145+
get => _bufferAllocationThresholds.MaxStackAllocLength;
146+
set => _bufferAllocationThresholds.MaxStackAllocLength = value;
147+
}
148+
149+
/// <summary>
150+
/// The maximum length of a buffer that can be rented from the array pool when formatting a <see cref="DateOnlyNode"/>.
151+
/// Buffers larger than this will be allocated on the heap.
152+
/// </summary>
153+
public int MaxPooledArrayLength
154+
{
155+
get => _bufferAllocationThresholds.MaxPooledArrayLength;
156+
set => _bufferAllocationThresholds.MaxPooledArrayLength = value;
157+
}
158+
}

src/StringEnricher/Configuration/StringEnricherSettings.Extensions.StringBuilder.cs

Lines changed: 10 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ public static partial class Extensions
99
{
1010
private const string Name = $"{StringEnricherSettings.Name}.{nameof(Extensions)}";
1111

12+
private static BufferAllocationThresholds GetDefaultStringBuilderSettings() => new(
13+
name: $"{Name}.{nameof(StringBuilder)}",
14+
maxStackAllocLength: 512, maxPooledArrayLength: 1_000_000
15+
);
16+
1217
/// <summary>
1318
/// Configuration settings for StringBuilder-related optimizations.
1419
/// These settings help balance performance and memory usage when appending StringEnricher nodes to StringBuilder.
@@ -17,45 +22,11 @@ public static partial class Extensions
1722
/// Nodes larger than this will use direct heap allocation.
1823
/// Adjust these values based on your application's performance and memory usage characteristics.
1924
/// </summary>
20-
public static class StringBuilder
21-
{
22-
private const string Name = $"{Extensions.Name}.{nameof(StringBuilder)}";
23-
24-
private static BufferAllocationThresholds _bufferAllocationThresholds = new(Name, 512, 1_000_000);
25-
26-
/// <summary>
27-
/// The maximum length of a node that can be allocated on the stack.
28-
/// Nodes with a length less than or equal to this value will use stack allocation for optimal performance.
29-
/// Default is 512 characters.
30-
/// Note: Increasing this value may improve performance for larger nodes but also increases stack usage,
31-
/// which can lead to stack overflow in deep recursion scenarios. Adjust with caution.
32-
/// Recommended range is between 128 and 1024 characters.
33-
/// Values above 2048 are strongly discouraged due to potential stack overflow risks.
34-
/// Consider your application's typical node sizes and stack usage patterns when configuring this setting.
35-
/// </summary>
36-
public static int MaxStackAllocLength
37-
{
38-
get => _bufferAllocationThresholds.MaxStackAllocLength;
39-
set => _bufferAllocationThresholds.MaxStackAllocLength = value;
40-
}
25+
public static BufferAllocationThresholds StringBuilder = GetDefaultStringBuilderSettings();
4126

42-
/// <summary>
43-
/// The maximum length of a node that can use array pooling.
44-
/// Nodes with a length greater than MaxStackAllocLength and less than or equal to this value
45-
/// will use array pooling to reduce memory allocations and pressure on the garbage collector.
46-
/// Nodes larger than this will use direct heap allocation.
47-
/// Default is 1,000,000 characters.
48-
/// Note: Increasing this value may reduce heap allocations for larger nodes but also increases memory usage
49-
/// and pressure on the garbage collector. Adjust with caution.
50-
/// Recommended range is between 100,000 and 5,000,000 characters.
51-
/// Values above 10,000,000 are strongly discouraged due to potential excessive memory usage.
52-
/// Consider your application's typical node sizes and memory usage patterns when configuring this setting.
53-
/// </summary>
54-
public static int MaxPooledArrayLength
55-
{
56-
get => _bufferAllocationThresholds.MaxPooledArrayLength;
57-
set => _bufferAllocationThresholds.MaxPooledArrayLength = value;
58-
}
59-
}
27+
/// <summary>
28+
/// Resets the <see cref="StringBuilder"/> settings to their default values.
29+
/// </summary>
30+
public static void ResetStringBuilderSettings() => StringBuilder = GetDefaultStringBuilderSettings();
6031
}
6132
}

0 commit comments

Comments
 (0)