Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

luhndemo.gno

3.43 Kb · 114 lines
  1// Package luhndemo is a small gnoweb demo of the Luhn mod-10 checksum provided
  2// by the [p/moul/x/daily/luhn](/p/moul/x/daily/luhn/v0) library: put a number
  3// in the path and it tells you whether the check digit is right, and what it
  4// should have been.
  5//
  6// It contains no checksum logic of its own — validation, the check digit and
  7// the separator handling all come from the library. Read-only and stateless, so
  8// Render is fully deterministic.
  9package luhndemo
 10
 11import (
 12	"strconv"
 13	"strings"
 14
 15	"gno.land/p/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/md/v0"
 16	"gno.land/p/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/x/daily/luhn/v0"
 17)
 18
 19// samples are shown on the root page: a valid number, a typo'd one, and a
 20// transposition — the two error classes Luhn is designed to catch.
 21var samples = []struct {
 22	number string
 23	note   string
 24}{
 25	{"79927398713", "the textbook example"},
 26	{"79927398710", "same number, wrong check digit"},
 27	{"4539148803436467", "a test card number"},
 28	{"4539148803436647", "same card, two digits swapped"},
 29}
 30
 31// Render renders the checker for gnoweb.
 32//
 33//	Render("")        / Render("/") -> usage + worked samples
 34//	Render("/<digits>")             -> verdict for that number
 35func Render(path string) string {
 36	in := parseInput(path)
 37
 38	var b strings.Builder
 39	b.WriteString("# Luhn Checksum\n\n")
 40	b.WriteString("The mod-10 check behind card numbers and IMEIs, demoing the ")
 41	b.WriteString("[`p/moul/x/daily/luhn`](/p/moul/x/daily/luhn/v0) library.\n\n")
 42
 43	if in == "" {
 44		b.WriteString("Append a number to the path to check it — spaces and hyphens are ignored.\n\n")
 45		b.WriteString("## Samples\n\n")
 46		b.WriteString("| number | valid? | note |\n|---|---|---|\n")
 47		for _, s := range samples {
 48			b.WriteString("| `")
 49			b.WriteString(s.number)
 50			b.WriteString("` | ")
 51			b.WriteString(verdict(luhn.Valid(s.number)))
 52			b.WriteString(" | ")
 53			b.WriteString(s.note)
 54			b.WriteString(" |\n")
 55		}
 56		b.WriteString("\n> Luhn is a **typo check, not a security check**: it catches every ")
 57		b.WriteString("single-digit error and nearly every swap of adjacent digits, but ")
 58		b.WriteString("anyone can compute a valid number. Never authorize anything with it.\n")
 59		return b.String()
 60	}
 61
 62	b.WriteString("## " + md.InlineCode(in) + "\n\n")
 63
 64	if luhn.Valid(in) {
 65		b.WriteString("**Valid** — the check digit matches.\n")
 66		return b.String()
 67	}
 68
 69	b.WriteString("**Not valid.**\n\n")
 70	// Offer the fix: treat everything but the last digit as the payload.
 71	if payload, ok := trimLast(in); ok {
 72		if d, ok2 := luhn.CheckDigit(payload); ok2 {
 73			b.WriteString("Keeping the leading digits, the check digit should be **")
 74			b.WriteString(strconv.Itoa(d))
 75			b.WriteString("**")
 76			if fixed, ok3 := luhn.Append(payload); ok3 {
 77				b.WriteString(" — i.e. `")
 78				b.WriteString(fixed)
 79				b.WriteString("`")
 80			}
 81			b.WriteString(".\n")
 82			return b.String()
 83		}
 84	}
 85	b.WriteString("It has no usable digits to check.\n")
 86	return b.String()
 87}
 88
 89// parseInput extracts the first path segment.
 90func parseInput(path string) string {
 91	s := strings.TrimSpace(path)
 92	s = strings.TrimPrefix(s, "/")
 93	if i := strings.IndexByte(s, '/'); i >= 0 {
 94		s = s[:i]
 95	}
 96	return s
 97}
 98
 99// trimLast drops the final digit, ignoring separators, to recover the payload.
100func trimLast(s string) (string, bool) {
101	for i := len(s) - 1; i >= 0; i-- {
102		if s[i] >= '0' && s[i] <= '9' {
103			return s[:i], true
104		}
105	}
106	return "", false
107}
108
109func verdict(ok bool) string {
110	if ok {
111		return "✅ valid"
112	}
113	return "❌ invalid"
114}