ananoid 1.1.0

Edit this page

How-To: Define a Custom Alphabet

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.

Learning about 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:

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

Dealing with failures

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
  "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
  $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
  $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$"

Related Reading

Copyright

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