The default settings for creating a NanoId
(21 characters taken from a mix of letters, numbers, hyphen, and underscore)
reflect a reasonable balance of entropy versus performance. Further the
additional alphabets shipped with Ananoid cover a wide range of common needs.
But it is possible to go further. Consumers can define their own alphabets.
Conceptually, an 'alphabet' is a set of 'letters' (technically, single-byte
characters) from which a NanoId is constituted. In practice, an
Alphabet instance represents a valildated set of
letters. Specifically, an Alphabet is safe to use for the generation and
parsing of nano identifiers because it upholds the following invariants:
- The set of letters is NOT
null.
- The set of letters MUST contain at least one (1) non-whitespace letter.
- The set of letters MAY NOT contains more than 255 letters.
These are not the most challenging invariants, and any set of letters which
conforms to them can be validated as an Alphabet. For example, one could
define an alphabet consisting entirely of upper case ASCII letters:
F#
let uppercase = Alphabet.Validate("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
printfn $"Is alphabet valid? %b{Result.isOk uppercase}"
VB
Dim uppercase = Alphabet.Validate("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
WriteLine($"Is alphabet valid? {uppercase.IsOk}")
|
C#
var uppercase = Alphabet.Validate("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
WriteLine($"Is alphabet valid? {uppercase.IsOk}");
|
OUT
> dotnet fsi ~/scratches/definecustom.fsx
Is alphabet valid? true
|
Not all letter sets will be valid. When validation fails, Ananoid provides the
InvalidAlphabet type, which provides details about
why, exactly, a given set of letters is not valid.
F#
open AlphabetPatterns // ⮜⮜⮜ contains the `(|Letters|)` active pattern
match Alphabet.ofLetters String.Empty with
| Ok valid -> printfn $"%s{valid.Letters} are valid."
| Error(Letters invalid) when 255 < String.length invalid -> printfn "Too large: '%s{invalid}'!"
| Error(Letters invalid) when String.length invalid < 1 -> printfn "Too small: '%s{invalid}'!"
VB
Dim checked = String.Empty.ToAlphabet()
If checked.IsOk Then
Dim alphabet = checked.ResultValue
WriteLine($"{alphabet.Letters} are valid.")
Else
Dim [error] = checked.ErrorValue
Select True
Case 255 < [error].Letters.Length
WriteLine($"Too large: '{[error].Letters}'!")
Case [error].Letters.Length < 1
WriteLine($"Too small: '{[error].Letters}'!")
Case Else
Throw New UnreachableException()
End Select
End If
|
C#
var @checked = string.Empty.ToAlphabet();
var message = @checked switch
{
{ IsOk: true, ResultValue: var alphabet } => $"{alphabet.Letters} are valid.",
{ ErrorValue: { Letters: var letters} } when 255 < letters.Length => $"Too large: '{letters}'!",
{ ErrorValue: { Letters: var letters} } when letters.Length < 1 => $"Too small: '{letters}'!",
_ => throw new UnreachableException()
};
WriteLine(message);
|
OUT
> dotnet fsi ~/scratches/definecustom.fsx
Too small: ''!
|
However, sometimes, processing failures is uncessary (or, at least, unwanted).
In those cases, Ananoid has helper methods which reduce success-or-failure to a
boolean condition. Consider the following:
F#
match String.Empty.TryMakeAlphabet() with
| (true, alphabet) -> printfn $"%s{alphabet.Letters} are valid."
| (false, _) -> printfn "Too small: ''!"
VB
Dim alphabet As Alphabet = Nothing
Dim isOkay = String.Empty.TryMakeAlphabet(alphabet)
If Not isOkay AndAlso alphabet Is Nothing Then
WriteLine("Too small: ''!")
Else
WriteLine($"{alphabet.Letters} are valid.")
End If
|
C#
if ("".TryMakeAlphabet(out var alphabet))
{
Console.WriteLine($"{alphabet.Letters} are valid.");
}
else
{
Console.WriteLine("Too small: ''!");
}
|
OUT
> dotnet fsi ~/scratches/definecustom.fsx
Too small: ''!
|
Further, Ananoid can escalate failures by raising a
ArgumentOutOfRangeException, which surfaces details from an
InvalidAlphabet while also halting program flow and
capturing a stack trace. This is shown in the following example:
F#
try
let alphabet = Alphabet.makeOrRaise ("$" |> String.replicate 800)
printfn $"%s{alphabet.Letters} are valid."
with
| :? ArgumentOutOfRangeException as x -> printfn $"FAIL! %s{x.Message}"
VB
Try
Dim letters = New String("$"c, 800)
Dim alphabet = letters.ToAlphabetOrThrow()
WriteLine($"{alphabet.Letters} are valid.")
Catch x As ArgumentOutOfRangeException
WriteLine($"FAIL! {x.Message}")
End Try
|
C#
try
{
var alphabet = new String('$', 300).ToAlphabetOrThrow();
WriteLine($"{alphabet.Letters} are valid.");
}
catch (ArgumentOutOfRangeException x)
{
WriteLine($"FAIL! {x.Message}");
}
|
OUT
> dotnet fsi ~/scratches/definecustom.fsx
FAIL! must be between 1 and 255 letters (Parameter 'letters')
|
The library is available under the Mozilla Public License, Version 2.0.
For more information see the project's License file.
val uppercase: Result<obj,obj>
val printfn: format: Printf.TextWriterFormat<'T> -> 'T
Multiple items
module Result
from Microsoft.FSharp.Core
--------------------
[<Struct>]
type Result<'T,'TError> =
| Ok of ResultValue: 'T
| Error of ErrorValue: 'TError
val isOk: result: Result<'T,'Error> -> bool
module String
from Microsoft.FSharp.Core
union case Result.Ok: ResultValue: 'T -> Result<'T,'TError>
union case Result.Error: ErrorValue: 'TError -> Result<'T,'TError>
val length: str: string -> int
val replicate: count: int -> str: string -> string