Files
zeekay 9ab6a9a0e5 base: the table wire is ours, named in our own words
The wire carried another vendor's namespace in its error codes (PGRST1xx) and
in the media type a caller sends to claim a single row. Both ends are ours, so
it said someone else's name for no reason a caller could act on.

Two vocabularies meet in a fault and the split is the point: a constraint the
STORE refused answers with the store's own SQLSTATE, because every SQL engine
already agrees on how to say that; a request the WIRE would not accept answers
with a word of ours — bad_query, bad_body, not_one_row, refused, not_found,
store_failed — because that is a fact about this API and nobody else gets to
name it. Cardinality is asked for with application/vnd.hanzo.object+json.

LLM.md described the product as a translation of somebody else's seven
services, mapping their names onto ours, which is both wrong (their auth is not
Hanzo IAM) and backwards. It says what Base is: one binary, one file, the
surfaces it serves, and rules enforced here in Go rather than pushed down.

plugins/zap/forward.go still called itself additive to four native message
types that plugin.go had already removed — one node, one way in, and the table
wire arrives over it like every other route.
2026-08-13 22:46:01 -07:00

144 lines
4.2 KiB
Go

package validators
import (
"database/sql"
"errors"
"strings"
validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/hanzoai/dbx"
)
// UniqueId checks whether a field string id already exists in the specified table.
//
// Example:
//
// validation.Field(&form.RelId, validation.By(validators.UniqueId(form.app.DB(), "tbl_example"))
func UniqueId(db dbx.Builder, tableName string) validation.RuleFunc {
return func(value any) error {
v, _ := value.(string)
if v == "" {
return nil // nothing to check
}
var foundId string
err := db.
Select("id").
From(tableName).
Where(dbx.HashExp{"id": v}).
Limit(1).
Row(&foundId)
if (err != nil && !errors.Is(err, sql.ErrNoRows)) || foundId != "" {
return validation.NewError("validation_invalid_or_existing_id", "The model id is invalid or already exists.")
}
return nil
}
}
// NormalizeUniqueIndexError attempts to convert a
// "unique constraint failed" error into a validation.Errors.
//
// The provided err is returned as it is without changes if:
// - err is nil
// - err is already validation.Errors
// - err is not "unique constraint failed" error
func NormalizeUniqueIndexError(err error, tableOrAlias string, fieldNames []string) error {
if err == nil {
return err
}
if _, ok := err.(validation.Errors); ok {
return err
}
msg := strings.ToLower(err.Error())
if !IsUniqueViolation(err) {
return err
}
// Postgres names the INDEX rather than the columns
// ("duplicate key value violates unique constraint \"idx_unique_demo2_title\""),
// so the field is recovered from the index name instead. Base builds those
// names out of the column, and matching a whole underscore-delimited token
// rather than a substring is what keeps a field named "e" from matching
// every index that happens to contain the letter.
if !strings.Contains(msg, "unique constraint failed") {
if idx := uniqueIndexName(msg); idx != "" {
tokens := map[string]bool{}
for _, t := range strings.Split(idx, "_") {
tokens[t] = true
}
normalizedErrs := validation.Errors{}
for _, name := range fieldNames {
if tokens[strings.ToLower(name)] {
normalizedErrs[name] = validation.NewError("validation_not_unique", "Value must be unique")
}
}
if len(normalizedErrs) > 0 {
return normalizedErrs
}
}
return err
}
// check for unique constraint failure
if strings.Contains(msg, "unique constraint failed") {
// note: extra space to unify multi-columns lookup
msg = strings.ReplaceAll(strings.TrimSpace(msg), ",", " ") + " "
normalizedErrs := validation.Errors{}
for _, name := range fieldNames {
// note: extra spaces to exclude table name with suffix matching the current one
// OR other fields starting with the current field name
if strings.Contains(msg, strings.ToLower(" "+tableOrAlias+"."+name+" ")) {
normalizedErrs[name] = validation.NewError("validation_not_unique", "Value must be unique")
}
}
if len(normalizedErrs) > 0 {
return normalizedErrs
}
}
return err
}
// IsUniqueViolation reports whether err is the engine's way of saying a unique
// index was violated.
//
// The two engines this store runs on word it completely differently, and the
// literal match this used to be ("unique constraint failed") is SQLite's alone —
// so on Postgres a duplicate never normalized into a field error and surfaced as
// a 500 instead of a 400. It is also what the table wire reads to answer with
// SQLSTATE 23505, which is the code REST clients branch on.
func IsUniqueViolation(err error) bool {
if err == nil {
return false
}
msg := strings.ToLower(err.Error())
return strings.Contains(msg, "unique constraint failed") || // sqlite
strings.Contains(msg, "duplicate key value violates unique constraint") || // postgres
strings.Contains(msg, "sqlstate 23505") // postgres, when the driver reports the code
}
// uniqueIndexName pulls the quoted index name out of a Postgres duplicate-key
// message, or returns "" when there is not one to read.
func uniqueIndexName(msg string) string {
const marker = `unique constraint "`
i := strings.Index(msg, marker)
if i < 0 {
return ""
}
rest := msg[i+len(marker):]
j := strings.IndexByte(rest, '"')
if j < 0 {
return ""
}
return rest[:j]
}