Files
backend_fiesta/models/flexibleInt.go
2026-09-02 16:01:24 +05:30

117 lines
2.9 KiB
Go

package models
import (
"database/sql/driver"
"encoding/json"
"fmt"
"strconv"
"strings"
)
// FlexibleInt is an integer column that accepts what clients actually send.
//
// The counterpart to FlexibleString, and it exists for the same reason: a strict
// type here does not reject one field, it rejects the WHOLE request. Fiber's
// BodyParser fails on the first field it cannot convert, so `"status": "Active"`
// or `"primaryaddress": true` returns 400 "Invalid request body" and nothing is
// saved. On the address form that surfaced as a shopper adding a delivery
// address, seeing no error worth acting on, and then finding no address to pick
// at checkout — measured 2026-09-02: three plausible payloads, three 400s.
//
// Accepted, in the shapes a form or a phone actually produces:
//
// 1 a number
// "1" a number as a string, which is what a text input gives
// true a checkbox — "set as my default address"
// "" an untouched field, read as 0 rather than an error
// "Active" a word, mapped by meaning: active/yes/true/default are 1
//
// A word it does not recognise is an error, not a silent 0. Guessing there
// would store something the shopper did not choose.
type FlexibleInt int
func (fi *FlexibleInt) UnmarshalJSON(b []byte) error {
if len(b) == 0 || string(b) == "null" {
return nil
}
if b[0] == '"' {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
s = strings.TrimSpace(s)
if s == "" {
*fi = 0
return nil
}
if n, err := strconv.Atoi(s); err == nil {
*fi = FlexibleInt(n)
return nil
}
switch strings.ToLower(s) {
case "active", "yes", "true", "default", "primary":
*fi = 1
case "inactive", "no", "false":
*fi = 0
default:
return fmt.Errorf("cannot read %q as a number", s)
}
return nil
}
if b[0] == 't' || b[0] == 'f' {
var v bool
if err := json.Unmarshal(b, &v); err != nil {
return err
}
if v {
*fi = 1
} else {
*fi = 0
}
return nil
}
// A number, possibly written with a decimal point by a client that has no
// integer type of its own.
var f float64
if err := json.Unmarshal(b, &f); err != nil {
return err
}
*fi = FlexibleInt(int(f))
return nil
}
func (fi FlexibleInt) MarshalJSON() ([]byte, error) {
return json.Marshal(int(fi))
}
// Scan reads the column back. bigint arrives as int64; the string cases cover a
// text column that holds a number, which this schema has in places.
func (fi *FlexibleInt) Scan(value interface{}) error {
switch v := value.(type) {
case nil:
*fi = 0
case int64:
*fi = FlexibleInt(v)
case int:
*fi = FlexibleInt(v)
case float64:
*fi = FlexibleInt(int(v))
case []byte:
n, _ := strconv.Atoi(strings.TrimSpace(string(v)))
*fi = FlexibleInt(n)
case string:
n, _ := strconv.Atoi(strings.TrimSpace(v))
*fi = FlexibleInt(n)
default:
return fmt.Errorf("cannot read %T as a number", value)
}
return nil
}
func (fi FlexibleInt) Value() (driver.Value, error) {
return int64(fi), nil
}