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
AlphabetError type, which provides details about
why, exactly, a given set of letters is not valid.
F#
match Alphabet.ofLetters String.Empty with
| Ok valid -> printfn $"%s{valid.Letters} are valid."
| Error(AlphabetTooLarge letters) -> printfn "Too large: '%s{letters}'!"
| Error(AlphabetTooSmall letters) -> printfn "Too small: '%s{letters}'!"
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 [error].IsAlphabetTooLarge
WriteLine($"Too large: '{[error].Letters}'!")
Case [error].IsAlphabetTooSmall
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: var error } => error switch
{
{ IsAlphabetTooLarge: true } => $"Too large: '{error.Letters}'!",
{ IsAlphabetTooSmall: true } => $"Too small: '{error.Letters}'!",
_ => throw new UnreachableException()
},
_ => throw new UnreachableException()
};
WriteLine(message);
|
OUT
> dotnet fsi ~/scratches/definecustom.fsx
Too small: ''!
|
However, sometimes, processing complex failures is uncessary (or, at least,
undesirable). In those cases, Ananoid can raise an
AlphabetException, which not only surfaces an
AlphabetError
but also halts program flow and captures a stack trace. This
is shown in the following example:
F#
try
Alphabet.makeOrRaise (String.replicate 800 "$")
with
| :? AlphabetException as x -> printfn $"FAIL! %A{x.Reason}"
VB
Try
Dim letters = New String("$"c, 800)
Dim alphabet = letters.ToAlphabetOrThrow()
WriteLine($"{alphabet.Letters} are valid.")
Catch x As AlphabetException
WriteLine($"FAIL! {x.Reason}")
End Try
|
C#
try
{
var alphabet = new String('$', 300).ToAlphabetOrThrow();
WriteLine($"{alphabet.Letters} are valid.");
}
catch (AlphabetException x)
{
WriteLine($"FAIL! {x.Reason}");
}
|
OUT
> dotnet fsi ~/scratches/definecustom.fsx
FAIl! AlphabetTooLarge
"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$"
|
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 replicate: count: int -> str: string -> string