131 lines
3.0 KiB
Markdown
131 lines
3.0 KiB
Markdown
Copyright (c) 2026 Micha Hoiting
|
||
|
||
# triplex
|
||
|
||
[](https://git.hoiting.org/micha/triplex/actions)
|
||
<!-- TODO: [](https://github.com/micha/triplex/actions/workflows/ci.yml) -->
|
||
|
||
`triplex` is a deterministic, reversible serial number engine for
|
||
high-integrity identifiers. It maps a compact 32-bit integer space to a
|
||
human-readable serial format:
|
||
|
||
```
|
||
LLL-NNN-LC
|
||
```
|
||
|
||
Where:
|
||
- `L` = data letter (`A`–`Z`)
|
||
- `N` = number (`100`–`999`)
|
||
- `C` = checksum letter (`A`–`Z`)
|
||
|
||
## What it provides
|
||
|
||
- 4 data letters
|
||
- 3 numeric digits (`100`–`999`)
|
||
- 1 checksum letter
|
||
- Optional forbidden-triplet filtering on the first three letters
|
||
- Reversible integer encoding/decoding
|
||
- Deterministic checksum generation
|
||
|
||
Total index capacity: **386,942,400** (`26^4 * 900`)
|
||
|
||
## Features
|
||
|
||
- Reversible mapping: `Encode(code) ↔ Decode(index)`
|
||
- Deterministic checksum: `C = 'A' + (index % 26)`
|
||
- Compact index space: fits in `uint32`
|
||
- Human-readable serial format
|
||
- Useful for manufacturing, logistics, SaaS identifiers, and audit-safe systems
|
||
|
||
## Example
|
||
|
||
```go
|
||
package main
|
||
|
||
import (
|
||
"fmt"
|
||
|
||
"git.hoiting.org/micha/triplex/serial"
|
||
)
|
||
|
||
func main() {
|
||
idx := uint32(123456)
|
||
|
||
code, err := serial.Decode(idx)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
|
||
idx2, err := serial.Encode(code)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
|
||
fmt.Println(code)
|
||
fmt.Println(idx == idx2) // true
|
||
}
|
||
```
|
||
|
||
## Notes
|
||
|
||
- The current serial format is `LLL-NNN-LC`.
|
||
- Forbidden-triplet logic is centralized in `serial.IsForbiddenTriplet` and can be tailored to project rules.
|
||
|
||
## API
|
||
|
||
- `serial.Encode(code string) (uint32, error)`
|
||
- Parses and validates `LLL-NNN-LC`, checks checksum, and returns the deterministic index.
|
||
- `serial.Decode(idx uint32) (string, error)`
|
||
- Converts a valid index back to `LLL-NNN-LC`.
|
||
- `serial.RandomCode(isInUse ...func(uint32) bool) (string, uint32, error)`
|
||
- Generates a random valid `LLL-NNN-LC` code and its corresponding index.
|
||
- If provided, the callback is used to skip indices already in use by the client.
|
||
- `serial.RandomCodeWithOptions(options serial.RandomCodeOptions) (string, uint32, error)`
|
||
- Configurable variant with `MaxAttempts`, `IsInUse`, and custom `RandomIndex` source.
|
||
|
||
Example with options:
|
||
|
||
```go
|
||
// See the runnable command at ./cmd/triplex-example/main.go
|
||
```
|
||
|
||
## Commands
|
||
|
||
Runnable example command:
|
||
|
||
```bash
|
||
go run ./cmd/triplex-example
|
||
```
|
||
|
||
Using Makefile:
|
||
|
||
```bash
|
||
make run-example
|
||
make build-example
|
||
make test
|
||
```
|
||
|
||
Exported errors:
|
||
|
||
- `serial.ErrInvalidFormat`
|
||
- `serial.ErrInvalidLetters`
|
||
- `serial.ErrForbiddenLetterTriplet`
|
||
- `serial.ErrInvalidNumber`
|
||
- `serial.ErrNumberOutOfRange`
|
||
- `serial.ErrInvalidChecksum`
|
||
- `serial.ErrIndexOutOfRange`
|
||
- `serial.ErrRandomSourceFailed`
|
||
- `serial.ErrNoAvailableIndex`
|
||
|
||
## Testing
|
||
|
||
Run all tests:
|
||
|
||
```bash
|
||
go test ./...
|
||
```
|
||
|
||
## License
|
||
|
||
This project is licensed under the MIT License. See [LICENSE](LICENSE).
|