From 7071096e43d060f5715f397554ab3dd9a44f3ff1 Mon Sep 17 00:00:00 2001 From: Patchouli Date: Mon, 4 Nov 2024 15:08:27 -0500 Subject: [PATCH] Add GravatarHelper class and unit tests --- source/Gravatar.Tests/GravatarHelperTests.cs | 518 ++++++++++++++++++ source/Gravatar/EnumExtensions.cs | 33 ++ source/Gravatar/GravatarAvatarDefault.cs | 191 +++++++ source/Gravatar/GravatarAvatarRating.cs | 42 ++ source/Gravatar/GravatarHelper.cs | 335 +++++++++++ source/Gravatar/GravatarProfileQRCodeType.cs | 33 ++ .../Gravatar/GravatarProfileQRCodeVersion.cs | 26 + 7 files changed, 1178 insertions(+) create mode 100644 source/Gravatar.Tests/GravatarHelperTests.cs create mode 100644 source/Gravatar/EnumExtensions.cs create mode 100644 source/Gravatar/GravatarAvatarDefault.cs create mode 100644 source/Gravatar/GravatarAvatarRating.cs create mode 100644 source/Gravatar/GravatarHelper.cs create mode 100644 source/Gravatar/GravatarProfileQRCodeType.cs create mode 100644 source/Gravatar/GravatarProfileQRCodeVersion.cs diff --git a/source/Gravatar.Tests/GravatarHelperTests.cs b/source/Gravatar.Tests/GravatarHelperTests.cs new file mode 100644 index 0000000..e6ba3d7 --- /dev/null +++ b/source/Gravatar.Tests/GravatarHelperTests.cs @@ -0,0 +1,518 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +using Microsoft; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.TestTools; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Gravatar +{ + /// + /// + /// + [TestClass] + public sealed class GravatarHelperTests + { + /// + /// + /// + public static IEnumerable GetEmailAddressHashBytesTestData => + [ + [ + "self@patchouli.sh", + new byte[] { 252, 114, 73, 27, 133, 30, 239, 4, 152, 24, 47, 48, 154, 167, 28, 183 } + ], + ]; + + /// + /// + /// + public static IEnumerable GetEmailAddressHashTestData => + [ + [ + "self@patchouli.sh", + "fc72491b851eef0498182f309aa71cb7" + ], + ]; + + /// + /// + /// + public static IEnumerable GetProfileUrlTestData => + [ + [ + "self@patchouli.sh", + new Uri("https://gravatar.com/fc72491b851eef0498182f309aa71cb7.json") + ], + ]; + + /// + /// + /// + public static IEnumerable GetProfileQRCodeUrlTestData => + [ + [ + "self@patchouli.sh", + default(int?), + default(GravatarProfileQRCodeType?), + default(GravatarProfileQRCodeVersion?), + new Uri("https://gravatar.com/fc72491b851eef0498182f309aa71cb7.qr") + ], + [ + "self@patchouli.sh", + default(int?), + GravatarProfileQRCodeType.None, + default(GravatarProfileQRCodeVersion?), + new Uri("https://gravatar.com/fc72491b851eef0498182f309aa71cb7.qr?type=default") + ], + [ + "self@patchouli.sh", + default(int?), + GravatarProfileQRCodeType.Avatar, + default(GravatarProfileQRCodeVersion?), + new Uri("https://gravatar.com/fc72491b851eef0498182f309aa71cb7.qr?type=user") + ], + [ + "self@patchouli.sh", + default(int?), + GravatarProfileQRCodeType.Logo, + default(GravatarProfileQRCodeVersion?), + new Uri("https://gravatar.com/fc72491b851eef0498182f309aa71cb7.qr?type=gravatar") + ], + [ + "self@patchouli.sh", + default(int?), + default(GravatarProfileQRCodeType?), + GravatarProfileQRCodeVersion.Modern, + new Uri("https://gravatar.com/fc72491b851eef0498182f309aa71cb7.qr?version=3") + ], + [ + "self@patchouli.sh", + default(int?), + default(GravatarProfileQRCodeType?), + GravatarProfileQRCodeVersion.Standard, + new Uri("https://gravatar.com/fc72491b851eef0498182f309aa71cb7.qr?version=1") + ], + ]; + + /// + /// + /// + public static IEnumerable GetAvatarUrlTestData => + [ + [ + "self@patchouli.sh", + default(int?), + default(string), + default(bool?), + default(GravatarAvatarRating?), + default(bool?), + new Uri("https://gravatar.com/avatar/fc72491b851eef0498182f309aa71cb7") + ], + [ + "self@patchouli.sh", + default(int?), + GravatarAvatarDefault.Identicon + .ToString(), + default(bool?), + default(GravatarAvatarRating?), + default(bool?), + new Uri("https://gravatar.com/avatar/fc72491b851eef0498182f309aa71cb7?d=identicon") + ], + [ + "self@patchouli.sh", + default(int?), + GravatarAvatarDefault.Identicon + .ToString(), + false, + default(GravatarAvatarRating?), + default(bool?), + new Uri("https://gravatar.com/avatar/fc72491b851eef0498182f309aa71cb7?d=identicon&f=n") + ], + [ + "self@patchouli.sh", + default(int?), + GravatarAvatarDefault.Identicon + .ToString(), + true, + default(GravatarAvatarRating?), + default(bool?), + new Uri("https://gravatar.com/avatar/fc72491b851eef0498182f309aa71cb7?d=identicon&f=y") + ], + [ + "self@patchouli.sh", + default(int?), + GravatarAvatarDefault.Identicon + .ToString(), + true, + GravatarAvatarRating.G, + default(bool?), + new Uri("https://gravatar.com/avatar/fc72491b851eef0498182f309aa71cb7?d=identicon&f=y&r=g") + ], + [ + "self@patchouli.sh", + default(int?), + GravatarAvatarDefault.Identicon + .ToString(), + true, + default(GravatarAvatarRating?), + true, + new Uri("https://gravatar.com/avatar/fc72491b851eef0498182f309aa71cb7.jpg?d=identicon&f=y") + ], + ]; + + /// + /// + /// + [TestCategory( + "GravatarHelper")] + [TestCategory( + "GetEmailAddressHashBytes")] + [TestCategory( + "Argument")] + [TestMethod] + public void GetEmailAddressHashBytesThrowWhenNull() + { + Assert.ThrowsException( + () => GravatarHelper + .GetEmailAddressHashBytes( + default(string))); + } + + /// + /// + /// + [TestCategory( + "GravatarHelper")] + [TestCategory( + "GetEmailAddressHashBytes")] + [TestCategory( + "Argument")] + [TestMethod] + public void GetEmailAddressHashBytesThrowWhenEmpty() + { + Assert.ThrowsException( + () => GravatarHelper + .GetEmailAddressHashBytes( + String.Empty)); + } + + /// + /// + /// + [TestCategory( + "GravatarHelper")] + [TestCategory( + "GetEmailAddressHash")] + [TestCategory( + "Argument")] + [TestMethod] + public void GetEmailAddressHashThrowWhenNull() + { + Assert.ThrowsException( + () => GravatarHelper + .GetEmailAddressHash( + default(string))); + } + + /// + /// + /// + [TestCategory( + "GravatarHelper")] + [TestCategory( + "GetEmailAddressHash")] + [TestCategory( + "Argument")] + [TestMethod] + public void GetEmailAddressHashThrowWhenEmpty() + { + Assert.ThrowsException( + () => GravatarHelper + .GetEmailAddressHash( + String.Empty)); + } + + /// + /// + /// + [TestCategory( + "GravatarHelper")] + [TestCategory( + "GetProfileUrl")] + [TestCategory( + "Argument")] + [TestMethod] + public void GetProfileUrlThrowWhenNull() + { + Assert.ThrowsException( + () => GravatarHelper + .GetProfileUrl( + default(string))); + } + + /// + /// + /// + [TestCategory( + "GravatarHelper")] + [TestCategory( + "GetProfileUrl")] + [TestCategory( + "Argument")] + [TestMethod] + public void GetProfileUrlThrowWhenEmpty() + { + Assert.ThrowsException( + () => GravatarHelper + .GetProfileUrl( + String.Empty)); + } + + /// + /// + /// + [TestCategory( + "GravatarHelper")] + [TestCategory( + "GetProfileQRCodeUrl")] + [TestCategory( + "Argument")] + [TestMethod] + public void GetProfileQRCodeUrlThrowWhenNull() + { + Assert.ThrowsException( + () => GravatarHelper + .GetProfileQRCodeUrl( + default(string))); + } + + /// + /// + /// + [TestCategory( + "GravatarHelper")] + [TestCategory( + "GetProfileQRCodeUrl")] + [TestCategory( + "Argument")] + [TestMethod] + public void GetProfileQRCodeUrlThrowWhenEmpty() + { + Assert.ThrowsException( + () => GravatarHelper + .GetProfileQRCodeUrl( + String.Empty)); + } + + /// + /// + /// + [TestCategory( + "GravatarHelper")] + [TestCategory( + "GetAvatarUrl")] + [TestCategory( + "Argument")] + [TestMethod] + public void GetAvatarUrlThrowWhenNull() + { + Assert.ThrowsException( + () => GravatarHelper + .GetAvatarUrl( + default(string))); + } + + /// + /// + /// + [TestCategory( + "GravatarHelper")] + [TestCategory( + "GetAvatarUrl")] + [TestCategory( + "Argument")] + [TestMethod] + public void GetAvatarUrlThrowWhenEmpty() + { + Assert.ThrowsException( + () => GravatarHelper + .GetAvatarUrl( + String.Empty)); + } + + /// + /// + /// + /// + /// + [TestMethod] + [TestCategory( + "GravatarHelper")] + [TestCategory( + "GetEmailAddressHashBytes")] + [TestCategory( + "Result")] + [DynamicData( + nameof(GetEmailAddressHashBytesTestData))] + public void GetEmailAddressHashBytesTest( + string address, + byte[] expectedAddressHashBytes) + { + var actualAddressHashBytes = + GravatarHelper + .GetEmailAddressHashBytes( + address); + + for (var i = 0; i < actualAddressHashBytes.Length; i++) + { + Assert.AreEqual( + expectedAddressHashBytes[i], + actualAddressHashBytes[i]); + } + } + + /// + /// + /// + /// + /// + [TestMethod] + [TestCategory( + "GravatarHelper")] + [TestCategory( + "GetEmailAddressHash")] + [TestCategory( + "Result")] + [DynamicData( + nameof(GetEmailAddressHashTestData))] + public void GetEmailAddressHashTest( + string address, + string expectedAddressHash) + { + var actualAddressHash = + GravatarHelper + .GetEmailAddressHash( + address); + + Assert.AreEqual( + expectedAddressHash, + actualAddressHash, + // Gravatar is case-sensitive. + ignoreCase: false); + } + + /// + /// + /// + /// + /// + [TestMethod] + [TestCategory( + "GravatarHelper")] + [TestCategory( + "GetProfileUrl")] + [TestCategory( + "Result")] + [DynamicData( + nameof(GetProfileUrlTestData))] + public void GetProfileUrlTest( + string address, + Uri expectedUrl) + { + var actualUrl = + GravatarHelper + .GetProfileUrl( + address); + + Assert.AreEqual( + expectedUrl, + actualUrl); + } + + /// + /// + /// + /// + /// + /// + /// + /// + [TestMethod] + [TestCategory( + "GravatarHelper")] + [TestCategory( + "GetProfileQRCode")] + [TestCategory( + "Result")] + [DynamicData( + nameof(GetProfileQRCodeUrlTestData))] + public void GetProfileQRCodeUrlTest( + string address, + int? size, + GravatarProfileQRCodeType? type, + GravatarProfileQRCodeVersion? version, + Uri expectedUrl) + { + var actualUrl = + GravatarHelper + .GetProfileQRCodeUrl( + address, + size, + type, + version); + + Assert.AreEqual( + expectedUrl, + actualUrl); + } + + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + [TestMethod] + [TestCategory( + "GravatarHelper")] + [TestCategory( + "GetAvatarUrl")] + [TestCategory( + "Result")] + [DynamicData( + nameof(GetAvatarUrlTestData))] + public void GetAvatarUrlTest( + string address, + int? size, + string defaultValue, + bool? forceDefaultValue, + GravatarAvatarRating? rating, + bool? withFileExtension, + Uri expectedUrl) + { + var actualUrl = + GravatarHelper + .GetAvatarUrl( + address, + size, + (defaultValue != default) ? + new GravatarAvatarDefault( + defaultValue) : + null, + forceDefaultValue, + rating, + withFileExtension); + + Assert.AreEqual( + expectedUrl, + actualUrl); + } + } +} diff --git a/source/Gravatar/EnumExtensions.cs b/source/Gravatar/EnumExtensions.cs new file mode 100644 index 0000000..9308cfb --- /dev/null +++ b/source/Gravatar/EnumExtensions.cs @@ -0,0 +1,33 @@ +using System; +using System.Linq; +using System.Runtime; +using System.Runtime.Serialization; +using System.Reflection; + +namespace Gravatar +{ + /// + /// + /// + internal static class EnumExtensions + { + /// + /// + /// + /// + /// + /// + public static string ToMemberValue( + this TEnum value) + where TEnum : + struct, + Enum + { + return typeof(TEnum) + .GetMember(value.ToString()) + .FirstOrDefault()? + .GetCustomAttribute()? + .Value; + } + } +} diff --git a/source/Gravatar/GravatarAvatarDefault.cs b/source/Gravatar/GravatarAvatarDefault.cs new file mode 100644 index 0000000..ba5ccc1 --- /dev/null +++ b/source/Gravatar/GravatarAvatarDefault.cs @@ -0,0 +1,191 @@ +using System; +using System.Runtime; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; + +namespace Gravatar +{ + /// + /// + /// + [StructLayout( + LayoutKind.Sequential)] + public readonly struct GravatarAvatarDefault : + IEquatable + { + /// + /// + /// + public static GravatarAvatarDefault MysteryPerson => + new GravatarAvatarDefault( + "mp"); + + /// + /// + /// + public static GravatarAvatarDefault Identicon => + new GravatarAvatarDefault( + "identicon"); + + /// + /// + /// + public static GravatarAvatarDefault MonsterID => + new GravatarAvatarDefault( + "monsterid"); + + /// + /// + /// + public static GravatarAvatarDefault Wavatar => + new GravatarAvatarDefault( + "wavatar"); + + /// + /// + /// + public static GravatarAvatarDefault Retro => + new GravatarAvatarDefault( + "retro"); + + /// + /// + /// + public static GravatarAvatarDefault RoboHash => + new GravatarAvatarDefault( + "robohash"); + + /// + /// + /// + public static GravatarAvatarDefault Blank => + new GravatarAvatarDefault( + "blank"); + + /// + /// + /// + /// + /// + public static GravatarAvatarDefault FromUrl( + Uri url) => + FromUrl( + url?.ToString()); + + /// + /// + /// + /// + /// + /// + public static bool Equals( + GravatarAvatarDefault valueA, + GravatarAvatarDefault valueB) + { + return valueA.value == valueB.value; + } + + /// + /// + /// + /// + /// + public static GravatarAvatarDefault FromUrl( + string url) + { + ArgumentNullException + .ThrowIfNullOrEmpty( + url, + nameof(url)); + + return new GravatarAvatarDefault( + Uri.EscapeDataString(url)); + } + + private readonly string value; + + /// + /// + /// + /// + public GravatarAvatarDefault( + string value) + { + this.value = value; + } + + /// + /// + /// + /// + /// + public bool Equals( + GravatarAvatarDefault other) => + Equals( + this, + other); + + /// + /// + /// + /// + /// + public override bool Equals( + object obj) + { + if (obj is GravatarAvatarDefault other) + { + return Equals( + this, + other); + } + + return false; + } + + /// + /// + /// + /// + public override int GetHashCode() => + this.value + .GetHashCode(); + + /// + /// + /// + /// + public override string ToString() => + this.value; + + /// + /// + /// + /// + /// + /// + [MethodImpl( + MethodImplOptions.AggressiveInlining)] + public static bool operator ==( + GravatarAvatarDefault valueLeft, + GravatarAvatarDefault valueRight) => + Equals( + valueLeft, + valueRight); + + /// + /// + /// + /// + /// + /// + [MethodImpl( + MethodImplOptions.AggressiveInlining)] + public static bool operator !=( + GravatarAvatarDefault valueLeft, + GravatarAvatarDefault valueRight) => + !Equals( + valueLeft, + valueRight); + } +} diff --git a/source/Gravatar/GravatarAvatarRating.cs b/source/Gravatar/GravatarAvatarRating.cs new file mode 100644 index 0000000..e442a28 --- /dev/null +++ b/source/Gravatar/GravatarAvatarRating.cs @@ -0,0 +1,42 @@ +using System; +using System.Runtime; +using System.Runtime.Serialization; + +namespace Gravatar +{ + /// + /// + /// + public enum GravatarAvatarRating + { + /// + /// Suitable for display on all websites with any audience type. + /// + [EnumMember( + Value = "g")] + G, + + /// + /// May contain rude gestures, provocatively dressed individuals, + /// the lesser swear words, or mild violence. + /// + [EnumMember( + Value = "pg")] + PG, + + /// + /// May contain such things as harsh profanity, intense violence, + /// nudity, or hard drug use. + /// + [EnumMember( + Value = "r")] + R, + + /// + /// May contain sexual imagery or extremely disturbing violence. + /// + [EnumMember( + Value = "x")] + X + } +} diff --git a/source/Gravatar/GravatarHelper.cs b/source/Gravatar/GravatarHelper.cs new file mode 100644 index 0000000..63cb993 --- /dev/null +++ b/source/Gravatar/GravatarHelper.cs @@ -0,0 +1,335 @@ +using System; +using System.Net; +using System.Net.Mail; +using System.Linq; +using System.Text; +using System.Security; +using System.Security.Cryptography; +using System.Collections; +using System.Collections.Generic; + +namespace Gravatar +{ + /// + /// Contains helper methods for hashing email addresses and assembling various Gravatar URLs. + /// + public static class GravatarHelper + { + /// + /// + /// + private const string BaseProfileUrlValue = + "https://gravatar.com/"; + + /// + /// + /// + private const string BaseAvatarUrlValue = + "https://gravatar.com/avatar/"; + + /// + /// + /// + public static Uri BaseProfileUrl { get; private set; } = + new Uri(BaseProfileUrlValue); + + /// + /// + /// + public static Uri BaseAvatarUrl { get; private set; } = + new Uri(BaseAvatarUrlValue); + + /// + /// + /// + /// + /// + public static byte[] GetEmailAddressHashBytes( + MailAddress address) => + GetEmailAddressHashBytes( + address?.Address); + + /// + /// + /// + /// + /// + public static byte[] GetEmailAddressHashBytes( + string address) + { + ArgumentException + .ThrowIfNullOrEmpty( + address, + nameof(address)); + + return MD5.HashData( + Encoding.UTF8.GetBytes( + address + .Trim() + .ToLower())); + } + + /// + /// + /// + /// + /// + public static string GetEmailAddressHash( + MailAddress address) => + GetEmailAddressHash( + address?.Address); + + /// + /// + /// + /// + /// + public static string GetEmailAddressHash( + string address) + { + ArgumentException + .ThrowIfNullOrEmpty( + address, + nameof(address)); + + var addressHashBytes = + GetEmailAddressHashBytes( + address); + + var stringBuilder = new StringBuilder(32); + foreach (var addressHashByte in addressHashBytes) + { + stringBuilder.Append( + addressHashByte.ToString( + "x2")); + } + + return stringBuilder.ToString(); + } + + /// + /// + /// + /// + /// + public static Uri GetProfileUrl( + MailAddress address) => + GetProfileUrl( + address?.Address); + + /// + /// + /// + /// + /// + public static Uri GetProfileUrl( + string address) + { + ArgumentException + .ThrowIfNullOrEmpty( + address, + nameof(address)); + + var addressHash = + GetEmailAddressHash( + address); + + var url = + new UriBuilder( + BaseProfileUrlValue + + addressHash + + ".json"); + + return url.Uri; + } + + /// + /// + /// + /// + /// + /// + /// + /// + public static Uri GetProfileQRCodeUrl( + MailAddress address, + int? size = default, + GravatarProfileQRCodeType? type = default, + GravatarProfileQRCodeVersion? version = default) => + GetProfileQRCodeUrl( + address?.Address, + size, + type, + version); + + /// + /// + /// + /// + /// + /// + /// + /// + public static Uri GetProfileQRCodeUrl( + string address, + int? size = default, + GravatarProfileQRCodeType? type = default, + GravatarProfileQRCodeVersion? version = default) + { + ArgumentException + .ThrowIfNullOrEmpty( + address, + nameof(address)); + + var addressHash = + GetEmailAddressHash( + address); + + var url = + new UriBuilder( + BaseProfileUrlValue + + addressHash + + ".qr"); + + var urlQueryParams = + new Dictionary(); + + if (size.HasValue) + { + ArgumentOutOfRangeException + .ThrowIfNegativeOrZero( + size.Value, + nameof(size)); + + urlQueryParams["s"] = + size.GetValueOrDefault(); + } + + if (type.HasValue) + { + urlQueryParams["type"] = + type.GetValueOrDefault() + .ToMemberValue(); + } + + if (version.HasValue) + { + urlQueryParams["version"] = + version.GetValueOrDefault() + .ToMemberValue(); + } + + url.Query = + String.Join( + "&", + urlQueryParams + .Select(kvp => kvp.Key + "=" + kvp.Value.ToString())); + + return url.Uri; + } + + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static Uri GetAvatarUrl( + MailAddress address, + int? size = default, + GravatarAvatarDefault? defaultValue = default, + bool? forceDefaultValue = default, + GravatarAvatarRating? rating = default, + bool? withFileExtension = default) => + GetAvatarUrl( + address?.Address, + size, + defaultValue, + forceDefaultValue, + rating, + withFileExtension); + + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static Uri GetAvatarUrl( + string address, + int? size = default, + GravatarAvatarDefault? defaultValue = default, + bool? forceDefaultValue = default, + GravatarAvatarRating? rating = default, + bool? withFileExtension = default) + { + ArgumentException + .ThrowIfNullOrEmpty( + address, + nameof(address)); + + var addressHash = + GetEmailAddressHash( + address); + + var url = + new UriBuilder( + BaseAvatarUrlValue + + addressHash + + ((withFileExtension ?? false) ? + ".jpg" : + String.Empty)); + + var urlQueryParams = + new Dictionary(); + + if (size.HasValue) + { + ArgumentOutOfRangeException + .ThrowIfNegativeOrZero( + size.Value, + nameof(size)); + + urlQueryParams["s"] = + size.GetValueOrDefault(); + } + + if (defaultValue.HasValue) + { + urlQueryParams["d"] = + defaultValue.GetValueOrDefault() + .ToString(); + } + + if (forceDefaultValue.HasValue) + { + urlQueryParams["f"] = + forceDefaultValue.GetValueOrDefault() ? + "y" : "n"; + } + + if (rating.HasValue) + { + urlQueryParams["r"] = + rating.GetValueOrDefault() + .ToMemberValue(); + } + + url.Query = + String.Join( + "&", + urlQueryParams + .Select(kvp => kvp.Key + "=" + kvp.Value.ToString())); + + return url.Uri; + } + } +} diff --git a/source/Gravatar/GravatarProfileQRCodeType.cs b/source/Gravatar/GravatarProfileQRCodeType.cs new file mode 100644 index 0000000..db60d52 --- /dev/null +++ b/source/Gravatar/GravatarProfileQRCodeType.cs @@ -0,0 +1,33 @@ +using System; +using System.Runtime; +using System.Runtime.Serialization; + +namespace Gravatar +{ + /// + /// + /// + public enum GravatarProfileQRCodeType + { + /// + /// No user avatar or Gravatar logo. + /// + [EnumMember( + Value = "default")] + None, + + /// + /// The user's avatar. + /// + [EnumMember( + Value = "user")] + Avatar, + + /// + /// The Gravatar logo. + /// + [EnumMember( + Value = "gravatar")] + Logo, + } +} diff --git a/source/Gravatar/GravatarProfileQRCodeVersion.cs b/source/Gravatar/GravatarProfileQRCodeVersion.cs new file mode 100644 index 0000000..af32ca7 --- /dev/null +++ b/source/Gravatar/GravatarProfileQRCodeVersion.cs @@ -0,0 +1,26 @@ +using System; +using System.Runtime; +using System.Runtime.Serialization; + +namespace Gravatar +{ + /// + /// + /// + public enum GravatarProfileQRCodeVersion + { + /// + /// A standard QR code. + /// + [EnumMember( + Value = "1")] + Standard, + + /// + /// A modern "small dots" QR code. + /// + [EnumMember( + Value = "3")] + Modern, + } +}