Skip to content

Commit 7098a9f

Browse files
Merge pull request #24 from Stravaig-Projects/#22-static-shortcode
#22 Add a static class to gen short codes quickly
2 parents 424d61a + 327d7a7 commit 7098a9f

File tree

6 files changed

+262
-1
lines changed

6 files changed

+262
-1
lines changed

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,36 @@ You can also install using the package manager.
3434

3535
If you are going to be using extensions for the Microsoft Dependency Injection then you only need to install the `Straviag.ShortCode.DependencyInjection` package in your application entry assembly (the assembly in which you setup the dependency injection for your application) as it has a dependency on `Stravaig.ShortCode` already. In any other assembly in your application that needs to generate short codes, you will typically only need to install the `Stravaig.ShortCode` package.
3636

37+
## Fastest way to get started
38+
39+
There is a `static ShortCode` class that is preconfigures with some _reasonable_ defaults, although they can be overridden if desired.
40+
41+
To get a short code:
42+
43+
```csharp
44+
// To generate a 7 character random short code
45+
ShortCode.GenerateRandomShortCode();
46+
47+
// To generate short code of the given length
48+
ShortCode.GenerateRandomShortCode(int length);
49+
50+
// To generate a 7 character sequential short code
51+
ShortCode.GenerateSequentialShortCode();
52+
53+
// To generate sequential short code of the given length
54+
ShortCode.GenerateSequentialShortCode(int length);
55+
```
56+
57+
There are some configuration methods you can call during your app's startup:
58+
59+
* `ShortCode.SetLength(int)` sets the default length of the short codes that you want, defaults to 7.
60+
* `ShortCode.SetSequentialSeed(ulong)` sets the starting point for the sequential codes, otherwise it will reset to zero every time your app starts.
61+
* `ShortCode.SetCharacterSpace(string)` sets the characters that can be used in a short code, defaults to `NamedCharacterSpaces.LettersAndDigits` which is a list of unaccented Latin letters in lower and upper case and the digits zero to nine.
62+
* `ShortCode.Use<TGenerator>()` sets the random generator to use. `TGenerator` can be:
63+
- `GuidCodeGenerator`: The code is a hashed form of a GUID
64+
- `RandomCodeGenerator`: The code is generated from the `Random` class.
65+
- `CrytographicallyRandomCodeGenerator`: The code is generated using a cryptographic strength random number generator.
66+
3767
## Setting up Short Codes with Microsoft's Dependency Injection
3868

3969
If web apps this will be in your `Startup` class, somewhere in the `ConfigureServices` method. Otherwise it will go wherever you are adding your dependency to the service collection.

release-notes/wip-release-notes.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ Date: ???
88

99
### Features
1010

11+
* #22: Static methods for accessing Short codes more easily
12+
1113
### Miscellaneous
1214

1315
* Remove "IsDraft" flag from release task in the build.
1416

1517
### Dependabot
1618

19+
* Bump Moq from 4.15.2 to 4.16.0
1720

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using NUnit.Framework;
4+
using Shouldly;
5+
using Stravaig.ShortCode.Tests.__helpers;
6+
7+
namespace Stravaig.ShortCode.Tests
8+
{
9+
[TestFixture]
10+
public class ShortCodeTests
11+
{
12+
[SetUp]
13+
public void Reset()
14+
{
15+
var staticType = typeof(ShortCode);
16+
var constructor = staticType.TypeInitializer ?? throw new InvalidOperationException($"{nameof(ShortCode)} is expected to have a static constructor.");
17+
constructor.Invoke(null, Array.Empty<object>());
18+
}
19+
20+
[Test]
21+
public void DefaultSettings_HappyPath()
22+
{
23+
var sequentialCode1 = ShortCode.GenerateSequentialShortCode();
24+
sequentialCode1.Length.ShouldBe(7);
25+
sequentialCode1.ShouldBe("aaaaaab");
26+
27+
var sequentialCode2 = ShortCode.GenerateSequentialShortCode();
28+
sequentialCode2.Length.ShouldBe(7);
29+
sequentialCode2.ShouldBe("aaaaaac");
30+
31+
var randomCode1 = ShortCode.GenerateRandomShortCode();
32+
randomCode1.Length.ShouldBe(7);
33+
34+
var randomCode2 = ShortCode.GenerateRandomShortCode();
35+
randomCode2.Length.ShouldBe(7);
36+
randomCode1.ShouldNotBe(randomCode2);
37+
}
38+
39+
[Test]
40+
[TestCaseSource(sourceName: nameof(Lengths))]
41+
public void SetLength_HappyPath(int length)
42+
{
43+
ShortCode.SetDefaultLength(length);
44+
45+
var sequentialCode = ShortCode.GenerateSequentialShortCode();
46+
sequentialCode.Length.ShouldBe(length);
47+
48+
var randomCode = ShortCode.GenerateRandomShortCode();
49+
randomCode.Length.ShouldBe(length);
50+
}
51+
52+
[Test]
53+
[TestCase(10UL, "aaaaaal")]
54+
[TestCase(100UL, "aaaaabN")]
55+
public void SetSequentialSeed_HappyPath(ulong seed, string value)
56+
{
57+
ShortCode.SetSequentialSeed(seed);
58+
var sequentialCode = ShortCode.GenerateSequentialShortCode();
59+
sequentialCode.ShouldBe(value);
60+
}
61+
62+
[Test]
63+
[TestCase("0001111", "01")]
64+
[TestCase("AAABBBB", "AB")]
65+
[TestCase("AAAAADD", "ABCD")]
66+
[TestCase("0000030", "01234")]
67+
public void SetCharacterSpace_HappyPath(string value, string characterSpace)
68+
{
69+
ShortCode.SetSequentialSeed(14UL);
70+
ShortCode.SetCharacterSpace(characterSpace);
71+
var sequentialCode = ShortCode.GenerateSequentialShortCode();
72+
sequentialCode.ShouldBe(value);
73+
}
74+
75+
[Test]
76+
public void Use_SetsTheInternalGeneratorToRandom()
77+
{
78+
ShortCode.Use<RandomCodeGenerator>();
79+
dynamic shortCode = new StaticJailbreak(typeof(ShortCode));
80+
IShortCodeGenerator generator = (IShortCodeGenerator)shortCode._randomGenerator;
81+
82+
generator.ShouldNotBeNull();
83+
generator.ShouldBeOfType(typeof(RandomCodeGenerator));
84+
}
85+
86+
[Test]
87+
public void Use_SetsTheInternalGeneratorToGuid()
88+
{
89+
ShortCode.Use<GuidCodeGenerator>();
90+
dynamic shortCode = new StaticJailbreak(typeof(ShortCode));
91+
IShortCodeGenerator generator = (IShortCodeGenerator)shortCode._randomGenerator;
92+
93+
generator.ShouldNotBeNull();
94+
generator.ShouldBeOfType(typeof(GuidCodeGenerator));
95+
}
96+
97+
[Test]
98+
public void Use_SetsTheInternalGeneratorToCryptographicallyRandom()
99+
{
100+
ShortCode.Use<CryptographicallyRandomCodeGenerator>();
101+
dynamic shortCode = new StaticJailbreak(typeof(ShortCode));
102+
IShortCodeGenerator generator = (IShortCodeGenerator)shortCode._randomGenerator;
103+
104+
generator.ShouldNotBeNull();
105+
generator.ShouldBeOfType(typeof(CryptographicallyRandomCodeGenerator));
106+
}
107+
108+
private static IEnumerable<int> Lengths()
109+
{
110+
for (int i = 1; i <= 9; i++)
111+
yield return i;
112+
}
113+
}
114+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using System;
2+
using System.Dynamic;
3+
using System.Reflection;
4+
5+
namespace Stravaig.ShortCode.Tests.__helpers
6+
{
7+
public class StaticJailbreak : DynamicObject
8+
{
9+
private const MemberTypes IsPropertyOrField = MemberTypes.Property | MemberTypes.Field;
10+
private const BindingFlags AllAccessModifiers = BindingFlags.Public | BindingFlags.NonPublic;
11+
12+
private readonly Type _type;
13+
14+
public StaticJailbreak(Type staticType)
15+
{
16+
_type = staticType;
17+
}
18+
19+
public override bool TryGetMember(GetMemberBinder binder, out object result)
20+
{
21+
MemberInfo[] members = _type.GetMember(
22+
binder.Name,
23+
IsPropertyOrField,
24+
AllAccessModifiers | BindingFlags.Static | BindingFlags.GetField | BindingFlags.GetProperty);
25+
if (members.Length == 0)
26+
{
27+
result = null;
28+
return false;
29+
}
30+
31+
if (members.Length > 1)
32+
{
33+
throw new AmbiguousMatchException($"Found {members.Length} matches for {binder.Name}.");
34+
}
35+
36+
var member = members[0];
37+
if (member is FieldInfo field)
38+
{
39+
result = field.GetValue(null);
40+
return true;
41+
}
42+
43+
if (member is PropertyInfo property)
44+
{
45+
var method = property.GetMethod;
46+
if (method == null)
47+
{
48+
result = null;
49+
return false;
50+
}
51+
52+
result = method.Invoke(null, Array.Empty<object>());
53+
return true;
54+
}
55+
56+
throw new InvalidOperationException($"{member} is not a property or field.");
57+
}
58+
}
59+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
3+
namespace Stravaig.ShortCode
4+
{
5+
public static class ShortCode
6+
{
7+
private static int _defaultLength;
8+
private static Encoder _encoder;
9+
private static SequentialCodeGenerator _sequentialCodeGenerator;
10+
private static IShortCodeGenerator _randomGenerator;
11+
12+
static ShortCode()
13+
{
14+
_defaultLength = 7;
15+
_encoder = new Encoder(NamedCharacterSpaces.LettersAndDigits);
16+
_sequentialCodeGenerator = new SequentialCodeGenerator();
17+
_randomGenerator = new CryptographicallyRandomCodeGenerator();
18+
}
19+
20+
public static string GenerateSequentialShortCode(int? length = null)
21+
{
22+
ulong code = _sequentialCodeGenerator.GetNextCode();
23+
return _encoder.Convert(code, length ?? _defaultLength);
24+
}
25+
26+
public static string GenerateRandomShortCode(int? length = null)
27+
{
28+
ulong code = _randomGenerator.GetNextCode();
29+
return _encoder.Convert(code, length ?? _defaultLength);
30+
}
31+
32+
public static void SetSequentialSeed(ulong seed)
33+
{
34+
_sequentialCodeGenerator = new SequentialCodeGenerator(seed);
35+
}
36+
37+
public static void SetCharacterSpace(string characterSpace)
38+
{
39+
_encoder = new Encoder(characterSpace);
40+
}
41+
42+
public static void SetDefaultLength(int length)
43+
{
44+
int maxLength = _encoder.MaxLength();
45+
if (length <= 0) throw new ArgumentOutOfRangeException(nameof(length), $"Must be between 1 and {maxLength}");
46+
if (length > maxLength) throw new ArgumentOutOfRangeException(nameof(length), $"Must be between 1 and {maxLength}");
47+
_defaultLength = length;
48+
}
49+
50+
public static void Use<TGenerator>() where TGenerator : IShortCodeGenerator, new()
51+
{
52+
_randomGenerator = new TGenerator();
53+
}
54+
}
55+
}

version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.2.3
1+
0.3.0

0 commit comments

Comments
 (0)