fix(html): improve layout variant diagnostics
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-03 17:52:31 +00:00
parent a029931f76
commit 957bc85c64
2 changed files with 48 additions and 8 deletions

View file

@ -4,6 +4,7 @@ import (
"errors"
"maps"
"slices"
"strconv"
"strings"
)
@ -57,18 +58,17 @@ func NewLayout(variant string) *Layout {
// recognised slot characters.
// Example: ValidateLayoutVariant("HCF").
func ValidateLayoutVariant(variant string) error {
var invalid bool
var invalidSlots []byte
for i := range len(variant) {
if _, ok := slotRegistry[variant[i]]; ok {
continue
}
invalid = true
break
invalidSlots = append(invalidSlots, variant[i])
}
if !invalid {
if len(invalidSlots) == 0 {
return nil
}
return &layoutVariantError{variant: variant}
return &layoutVariantError{variant: variant, invalidSlots: invalidSlots}
}
// H appends nodes to the Header slot.
@ -182,13 +182,46 @@ func (l *Layout) Render(ctx *Context) string {
}
type layoutVariantError struct {
variant string
variant string
invalidSlots []byte
}
func (e *layoutVariantError) Error() string {
return "html: invalid layout variant " + e.variant
if e == nil {
return ErrInvalidLayoutVariant.Error()
}
var b strings.Builder
b.WriteString("html: invalid layout variant ")
b.WriteString(e.variant)
if len(e.invalidSlots) == 0 {
return b.String()
}
b.WriteString(" (invalid slot")
if len(e.invalidSlots) > 1 {
b.WriteString("s")
}
b.WriteString(": ")
for i, slot := range e.invalidSlots {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(strconv.QuoteRuneToASCII(rune(slot)))
}
b.WriteByte(')')
return b.String()
}
func (e *layoutVariantError) Unwrap() error {
return ErrInvalidLayoutVariant
}
// InvalidSlots returns a copy of the invalid slot characters that were present
// in the original variant string.
func (e *layoutVariantError) InvalidSlots() []byte {
if e == nil || len(e.invalidSlots) == 0 {
return nil
}
return append([]byte(nil), e.invalidSlots...)
}

View file

@ -179,7 +179,7 @@ func TestLayout_VariantError(t *testing.T) {
name: "mixed invalid variant",
variant: "HXC",
wantErr: true,
wantErrString: "html: invalid layout variant HXC",
wantErrString: "html: invalid layout variant HXC (invalid slot: 'X')",
wantRender: `<header role="banner" data-block="H-0">header</header>` +
`<main role="main" data-block="C-0">main</main>`,
},
@ -200,6 +200,13 @@ func TestLayout_VariantError(t *testing.T) {
if got := layout.VariantError().Error(); got != tt.wantErrString {
t.Fatalf("VariantError().Error() = %q, want %q", got, tt.wantErrString)
}
if err, ok := layout.VariantError().(*layoutVariantError); ok {
if got := string(err.InvalidSlots()); got != "X" {
t.Fatalf("InvalidSlots() = %q, want %q", got, "X")
}
} else {
t.Fatalf("VariantError() has unexpected concrete type %T", layout.VariantError())
}
} else if layout.VariantError() != nil {
t.Fatalf("VariantError() = %v, want nil", layout.VariantError())
}