Skip to content

Commit 40e03ef

Browse files
authored
Add dimension, metric and namespace validation (#37)
* add dimension, metric and namespace validation * rm unused exception ctors
1 parent baf7af6 commit 40e03ef

File tree

12 files changed

+220
-14
lines changed

12 files changed

+220
-14
lines changed

src/Amazon.CloudWatch.EMF/Constants.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@ namespace Amazon.CloudWatch.EMF
22
{
33
public class Constants
44
{
5+
public const int MAX_DIMENSION_SET_SIZE = 30;
6+
public const int MAX_DIMENSION_NAME_LENGTH = 250;
7+
public const int MAX_DIMENSION_VALUE_LENGTH = 1024;
8+
public const int MAX_METRIC_NAME_LENGTH = 1024;
9+
public const int MAX_NAMESPACE_LENGTH = 256;
10+
public const string VALID_NAMESPACE_REGEX = "^[a-zA-Z0-9._#:/-]+$";
11+
512
public const int DEFAULT_AGENT_PORT = 25888;
613

714
public const string UNKNOWN = "Unknown";
815

9-
public const int MAX_DIMENSION_SET_SIZE = 30;
10-
1116
public const int MAX_METRICS_PER_EVENT = 100;
1217

1318
public const string DEFAULT_NAMESPACE = "aws-embedded-metrics";

src/Amazon.CloudWatch.EMF/Exception/DimensionSetExceededException.cs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,5 @@ public DimensionSetExceededException()
99
". Account for default dimensions if not using SetDimensions.")
1010
{
1111
}
12-
13-
public DimensionSetExceededException(string message)
14-
: base(message)
15-
{
16-
}
17-
18-
public DimensionSetExceededException(string message, Exception inner)
19-
: base(message, inner)
20-
{
21-
}
2212
}
23-
}
13+
}

src/Amazon.CloudWatch.EMF/Exception/EMFClientException.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ public EMFClientException(string message, Exception inner)
1818
{
1919
}
2020
}
21-
}
21+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
3+
namespace Amazon.CloudWatch.EMF
4+
{
5+
public class InvalidDimensionException : Exception
6+
{
7+
public InvalidDimensionException(string message)
8+
: base(message)
9+
{
10+
}
11+
}
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
3+
namespace Amazon.CloudWatch.EMF
4+
{
5+
public class InvalidMetricException : Exception
6+
{
7+
public InvalidMetricException(string message)
8+
: base(message)
9+
{
10+
}
11+
}
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
3+
namespace Amazon.CloudWatch.EMF
4+
{
5+
public class InvalidNamespaceException : Exception
6+
{
7+
public InvalidNamespaceException(string message)
8+
: base(message)
9+
{
10+
}
11+
}
12+
}

src/Amazon.CloudWatch.EMF/Logger/MetricsLogger.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Amazon.CloudWatch.EMF.Config;
44
using Amazon.CloudWatch.EMF.Environment;
55
using Amazon.CloudWatch.EMF.Model;
6+
using Amazon.CloudWatch.EMF.Utils;
67
using Microsoft.Extensions.Logging;
78
using Microsoft.Extensions.Logging.Abstractions;
89

@@ -181,6 +182,7 @@ public MetricsLogger PutMetadata(string key, object value)
181182
/// <returns>the current logger.</returns>
182183
public MetricsLogger SetNamespace(string logNamespace)
183184
{
185+
Validator.ValidateNamespace(logNamespace);
184186
_context.Namespace = logNamespace;
185187
return this;
186188
}

src/Amazon.CloudWatch.EMF/Model/DimensionSet.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Collections.Generic;
22
using System.Linq;
3+
using Amazon.CloudWatch.EMF.Utils;
34

45
namespace Amazon.CloudWatch.EMF.Model
56
{
@@ -19,6 +20,7 @@ public DimensionSet()
1920
/// <param name="value">the value for the dimension</param>
2021
public DimensionSet(string key, string value)
2122
{
23+
Validator.ValidateDimensionSet(key, value);
2224
Dimensions[key] = value;
2325
}
2426

@@ -31,6 +33,7 @@ public DimensionSet(string key, string value)
3133
/// <param name="value">the dimension value</param>
3234
public void AddDimension(string key, string value)
3335
{
36+
Validator.ValidateDimensionSet(key, value);
3437
if (Dimensions.Count >= Constants.MAX_DIMENSION_SET_SIZE)
3538
throw new DimensionSetExceededException();
3639

src/Amazon.CloudWatch.EMF/Model/MetricsContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using Amazon.CloudWatch.EMF.Utils;
45

56
namespace Amazon.CloudWatch.EMF.Model
67
{
@@ -102,6 +103,7 @@ public bool HasDefaultDimensions
102103
/// <param name="unit">the units of the metric</param>
103104
public void PutMetric(string key, double value, Unit unit)
104105
{
106+
Validator.ValidateMetric(key, value);
105107
_metricDirective.PutMetric(key, value, unit);
106108
}
107109

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using System;
2+
using System.Text;
3+
4+
namespace Amazon.CloudWatch.EMF.Utils
5+
{
6+
public class Validator
7+
{
8+
internal static void ValidateDimensionSet(in string dimensionName, in string dimensionValue)
9+
{
10+
if (dimensionName == null || dimensionName.Trim().Length == 0)
11+
{
12+
throw new InvalidDimensionException("Dimension name must include at least one non-whitespace character");
13+
}
14+
15+
if (dimensionValue == null || dimensionValue.Trim().Length == 0)
16+
{
17+
throw new InvalidDimensionException("Dimension value must include at least one non-whitespace character");
18+
}
19+
20+
if (dimensionName.Length > Constants.MAX_DIMENSION_NAME_LENGTH)
21+
{
22+
throw new InvalidDimensionException($"Dimension name cannot be longer than {Constants.MAX_DIMENSION_NAME_LENGTH} characters: {dimensionName}");
23+
}
24+
25+
if (dimensionValue.Length > Constants.MAX_DIMENSION_VALUE_LENGTH)
26+
{
27+
throw new InvalidDimensionException($"Dimension value cannot be longer than {Constants.MAX_DIMENSION_VALUE_LENGTH} characters: {dimensionValue}");
28+
}
29+
30+
if (!IsAscii(dimensionName))
31+
{
32+
throw new InvalidDimensionException($"Dimension name contains invalid characters: {dimensionName}");
33+
}
34+
35+
if (!IsAscii(dimensionValue))
36+
{
37+
throw new InvalidDimensionException($"Dimension value contains invalid characters: {dimensionValue}");
38+
}
39+
40+
if (dimensionName.StartsWith(":"))
41+
{
42+
throw new InvalidDimensionException("Dimension name cannot start with ':'");
43+
}
44+
}
45+
46+
internal static void ValidateMetric(in string name, in double value)
47+
{
48+
if (name == null || name.Trim().Length == 0)
49+
{
50+
throw new InvalidMetricException($"Metric name {name} must include at least one non-whitespace character");
51+
}
52+
53+
if (name.Length > Constants.MAX_METRIC_NAME_LENGTH)
54+
{
55+
throw new InvalidMetricException($"Metric name {name} cannot be longer than {Constants.MAX_METRIC_NAME_LENGTH} characters");
56+
}
57+
58+
if (!Double.IsFinite(value))
59+
{
60+
throw new InvalidMetricException($"Metric value {value} must be a finite number");
61+
}
62+
}
63+
64+
internal static void ValidateNamespace(in string @namespace)
65+
{
66+
if (@namespace == null || @namespace.Trim().Length == 0)
67+
{
68+
throw new InvalidNamespaceException($"Namespace {@namespace} must include at least one non-whitespace character");
69+
}
70+
71+
if (@namespace.Length > Constants.MAX_NAMESPACE_LENGTH)
72+
{
73+
throw new InvalidNamespaceException($"Namespace {@namespace} cannot be longer than {Constants.MAX_NAMESPACE_LENGTH} characters");
74+
}
75+
76+
if (!System.Text.RegularExpressions.Regex.IsMatch(@namespace, Constants.VALID_NAMESPACE_REGEX))
77+
{
78+
throw new InvalidNamespaceException($"Namespace {@namespace} contains invalid characters");
79+
}
80+
}
81+
82+
private static bool IsAscii(in string str)
83+
{
84+
return Encoding.UTF8.GetByteCount(str) == str.Length;
85+
}
86+
}
87+
}

tests/Amazon.CloudWatch.EMF.Tests/Logger/MetricsLoggerTests.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ public MetricsLoggerTests()
3131

3232
_sink = new MockSink();
3333
_environment.Sink.Returns(_sink);
34+
_environment.LogGroupName.Returns("LogGroup");
35+
_environment.Name.Returns("Environment");
36+
_environment.Type.Returns("Type");
3437
_environmentProvider.ResolveEnvironment().Returns(_environment);
3538

3639
_metricsLogger = new MetricsLogger(_environmentProvider, _logger);
@@ -146,6 +149,25 @@ public void TestSetNameSpace()
146149
Assert.Equal(namespaceValue, _sink.MetricsContext.Namespace);
147150
}
148151

152+
[Theory]
153+
[InlineData(null)]
154+
[InlineData("")]
155+
[InlineData(" ")]
156+
[InlineData("namespace ")]
157+
[InlineData("ǹẚḿⱸṥṕấćē")]
158+
[InlineData("name$pace")]
159+
public void SetNamespace_WithInvalidNamespace_ThrowsInvalidNamespaceException(string namespaceValue)
160+
{
161+
Assert.Throws<InvalidNamespaceException>(() => _metricsLogger.SetNamespace(namespaceValue));
162+
}
163+
164+
[Fact]
165+
public void SetNamespace_WithNameTooLong_ThrowsInvalidNamespaceException()
166+
{
167+
string namespaceValue = new string('a', Constants.MAX_NAMESPACE_LENGTH + 1);
168+
Assert.Throws<InvalidNamespaceException>(() => _metricsLogger.SetNamespace(namespaceValue));
169+
}
170+
149171
[Fact]
150172
public void TestFlushWithConfiguredServiceName()
151173
{
@@ -216,6 +238,25 @@ public void TestPutMetricWithUnit()
216238
Assert.Equal(Unit.MILLISECONDS, metricDefinition.Unit);
217239
}
218240

241+
[Theory]
242+
[InlineData(null, 1)]
243+
[InlineData("", 1)]
244+
[InlineData(" ", 1)]
245+
[InlineData("metric", Double.PositiveInfinity)]
246+
[InlineData("metric", Double.NegativeInfinity)]
247+
[InlineData("metric", Double.NaN)]
248+
public void PutMetric_WithInvalidMetric_ThrowsInvalidMetricException(string metricName, double metricValue)
249+
{
250+
Assert.Throws<InvalidMetricException>(() => _metricsLogger.PutMetric(metricName, metricValue, Unit.NONE));
251+
}
252+
253+
[Fact]
254+
public void PutMetric_WithNameTooLong_ThrowsInvalidMetricException()
255+
{
256+
string metricName = new string('a', Constants.MAX_METRIC_NAME_LENGTH + 1);
257+
Assert.Throws<InvalidMetricException>(() => _metricsLogger.PutMetric(metricName, 1, Unit.NONE));
258+
}
259+
219260
[Fact]
220261
public void TestPutMetaData()
221262
{

tests/Amazon.CloudWatch.EMF.Tests/Model/DimensionSetTests.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Amazon.CloudWatch.EMF.Model;
2+
using Amazon.CloudWatch.EMF.Utils;
23
using Xunit;
34

45
namespace Amazon.CloudWatch.EMF.Tests.Model
@@ -25,6 +26,45 @@ public void AddDimension_30_Dimensions()
2526
Assert.Equal(dimensionSetSize, dimensionSet.DimensionKeys.Count);
2627
}
2728

29+
[Theory]
30+
[InlineData(null, "value")]
31+
[InlineData(" ", "value")]
32+
[InlineData("ďïɱ", "value")]
33+
[InlineData("dim", null)]
34+
[InlineData("dim", " ")]
35+
[InlineData("dim", "ⱱẵĺ")]
36+
[InlineData(":dim", "val")]
37+
public void AddDimension_WithInvalidValues_ThrowsInvalidDimensionException(string key, string value)
38+
{
39+
Assert.Throws<InvalidDimensionException>(() =>
40+
{
41+
var dimensionSet = new DimensionSet();
42+
dimensionSet.AddDimension(key, value);
43+
});
44+
}
45+
46+
[Fact]
47+
public void AddDimension_WithNameTooLong_ThrowsInvalidDimensionException()
48+
{
49+
Assert.Throws<InvalidDimensionException>(() =>
50+
{
51+
var dimensionSet = new DimensionSet();
52+
string dimensionName = new string('a', Constants.MAX_DIMENSION_NAME_LENGTH + 1);
53+
dimensionSet.AddDimension(dimensionName, "value");
54+
});
55+
}
56+
57+
[Fact]
58+
public void AddDimension_WithValueTooLong_ThrowsInvalidDimensionException()
59+
{
60+
Assert.Throws<InvalidDimensionException>(() =>
61+
{
62+
var dimensionSet = new DimensionSet();
63+
string dimensionValue = new string('a', Constants.MAX_DIMENSION_VALUE_LENGTH + 1);
64+
dimensionSet.AddDimension("name", dimensionValue);
65+
});
66+
}
67+
2868
[Fact]
2969
public void AddDimension_Limit_Exceeded_Error()
3070
{

0 commit comments

Comments
 (0)