Skip to content

Commit c62ca94

Browse files
mfahadahmedmikeproeng37
authored andcommitted
feat(event_builder): Don't track NaN, infinity and value greater than 2^53. (#121)
1 parent cb735ff commit c62ca94

File tree

2 files changed

+163
-7
lines changed

2 files changed

+163
-7
lines changed

OptimizelySDK.Tests/EventTests/EventBuilderTest.cs

Lines changed: 127 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2018, Optimizely
2+
* Copyright 2017-2019, Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -422,13 +422,17 @@ public void TestCreateImpressionEventRemovesInvalidAttributesFromPayload()
422422

423423
var userAttributes = new UserAttributes
424424
{
425-
{"device_type", "iPhone" },
426-
{"boolean_key", true },
427-
{"double_key", 3.14 },
425+
{ "device_type", "iPhone" },
426+
{ "boolean_key", true },
427+
{ "double_key", 3.14 },
428428
{ "", "Android" },
429429
{ "null", null },
430430
{ "objects", new object() },
431431
{ "arrays", new string[] { "a", "b", "c" } },
432+
{ "negative_infinity", double.NegativeInfinity },
433+
{ "positive_infinity", double.PositiveInfinity },
434+
{ "nan", double.NaN },
435+
{ "invalid_num_value", Math.Pow(2, 53) + 2 },
432436
};
433437

434438
var logEvent = EventBuilder.CreateImpressionEvent(Config, Config.GetExperimentFromKey("test_experiment"), "7722370027", TestUserId, userAttributes);
@@ -1719,5 +1723,124 @@ public void TestCreateConversionEventWhenEventUsedInMultipleExp()
17191723

17201724
Assert.IsTrue(TestData.CompareObjects(expectedLogEvent, logEvent));
17211725
}
1726+
1727+
[Test]
1728+
public void TestCreateConversionEventRemovesInvalidAttributesFromPayload()
1729+
{
1730+
var guid = Guid.NewGuid();
1731+
var timeStamp = TestData.SecondsSince1970();
1732+
1733+
var payloadParams = new Dictionary<string, object>
1734+
{
1735+
{"visitors", new object[]
1736+
{
1737+
new Dictionary<string, object>
1738+
{
1739+
{"snapshots", new object[]
1740+
{
1741+
new Dictionary<string, object>
1742+
{
1743+
{ "decisions", new object[]
1744+
{
1745+
new Dictionary<string, object>
1746+
{
1747+
{"campaign_id", "7719770039"},
1748+
{"experiment_id", "7716830082"},
1749+
{"variation_id", "7722370027"}
1750+
}
1751+
}
1752+
},
1753+
{"events", new object[]
1754+
{
1755+
new Dictionary<string, object>
1756+
{
1757+
{"entity_id", "7718020063"},
1758+
{"timestamp", timeStamp},
1759+
{"uuid", guid},
1760+
{"key", "purchase"},
1761+
}
1762+
}
1763+
}
1764+
}
1765+
}
1766+
},
1767+
{"visitor_id", TestUserId },
1768+
{"attributes", new object[]
1769+
{
1770+
new Dictionary<string, object>
1771+
{
1772+
{"entity_id", "7723280020" },
1773+
{"key", "device_type" },
1774+
{"type", "custom" },
1775+
{"value", "iPhone"}
1776+
},
1777+
new Dictionary<string, object>
1778+
{
1779+
{"entity_id", "323434545" },
1780+
{"key", "boolean_key" },
1781+
{"type", "custom" },
1782+
{"value", true}
1783+
},
1784+
new Dictionary<string, object>
1785+
{
1786+
{"entity_id", "808797686" },
1787+
{"key", "double_key" },
1788+
{"type", "custom" },
1789+
{"value", 3.14}
1790+
},
1791+
new Dictionary<string, object>
1792+
{
1793+
{"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE},
1794+
{"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE},
1795+
{"type", "custom" },
1796+
{"value", true }
1797+
}
1798+
}
1799+
}
1800+
}
1801+
}
1802+
},
1803+
{"project_id", "7720880029"},
1804+
{"account_id", "1592310167"},
1805+
{"client_name", "csharp-sdk"},
1806+
{"client_version", Optimizely.SDK_VERSION },
1807+
{"revision", "15" },
1808+
{"anonymize_ip", false}
1809+
};
1810+
1811+
var expectedEvent = new LogEvent(
1812+
"https://logx.optimizely.com/v1/events",
1813+
payloadParams,
1814+
"POST",
1815+
new Dictionary<string, string>
1816+
{
1817+
{ "Content-Type", "application/json"}
1818+
});
1819+
1820+
var userAttributes = new UserAttributes
1821+
{
1822+
{ "device_type", "iPhone" },
1823+
{ "boolean_key", true },
1824+
{ "double_key", 3.14 },
1825+
{ "", "Android" },
1826+
{ "null", null },
1827+
{ "objects", new object() },
1828+
{ "arrays", new string[] { "a", "b", "c" } },
1829+
{ "negative_infinity", double.NegativeInfinity },
1830+
{ "positive_infinity", double.PositiveInfinity },
1831+
{ "nan", double.NaN },
1832+
{ "invalid_num_value", Math.Pow(2, 53) + 2 },
1833+
};
1834+
1835+
var experimentToVariationMap = new Dictionary<string, Variation>
1836+
{
1837+
{"7716830082", new Variation{Id="7722370027", Key="control"} }
1838+
};
1839+
1840+
var logEvent = EventBuilder.CreateConversionEvent(Config, "purchase", experimentToVariationMap, TestUserId, userAttributes, null);
1841+
1842+
TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid);
1843+
Assert.IsTrue(TestData.CompareObjects(expectedEvent, logEvent));
1844+
}
17221845
}
17231846
}

OptimizelySDK/Utils/Validator.cs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2018, Optimizely
2+
* Copyright 2017-2019, Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,9 @@ namespace OptimizelySDK.Utils
2323
{
2424
public static class Validator
2525
{
26+
// Pow(2,53)
27+
public const double OPT_NUMBER_LIMIT = 9007199254740992;
28+
2629
/// <summary>
2730
/// Validate the ProjectConfig JSON
2831
/// </summary>
@@ -111,8 +114,38 @@ public static bool IsFeatureFlagValid(ProjectConfig projectConfig, FeatureFlag f
111114
public static bool IsUserAttributeValid(KeyValuePair<string, object> attribute)
112115
{
113116
return (attribute.Key != null) &&
114-
(attribute.Value is int || attribute.Value is string || attribute.Value is double
115-
|| attribute.Value is bool || attribute.Value is float || attribute.Value is long);
117+
(attribute.Value is string || attribute.Value is bool || IsValidNumericValue(attribute.Value));
118+
}
119+
120+
/// <summary>
121+
/// Validates if the type of provided value is numeric.
122+
/// </summary>
123+
/// <param name="value">true if the type of provided value is numeric, false otherwise</param>
124+
/// <returns></returns>
125+
public static bool IsNumericType(object value)
126+
{
127+
return value is byte || value is sbyte || value is char || value is short || value is ushort
128+
|| value is int || value is uint || value is long || value is ulong || value is float
129+
|| value is double || value is decimal;
130+
}
131+
132+
/// <summary>
133+
/// Validates if the provided value is a valid numeric value.
134+
/// </summary>
135+
/// <param name="value">Input value</param>
136+
/// <returns>true if the provided absolute value is not infinite, NAN and greater than 2^53, false otherwise</returns>
137+
public static bool IsValidNumericValue(object value)
138+
{
139+
if (IsNumericType(value))
140+
{
141+
var doubleValue = Convert.ToDouble(value);
142+
if (double.IsInfinity(doubleValue) || double.IsNaN(doubleValue) || Math.Abs(doubleValue) > OPT_NUMBER_LIMIT)
143+
return false;
144+
145+
return true;
146+
}
147+
148+
return false;
116149
}
117150
}
118151
}

0 commit comments

Comments
 (0)