Files
foundry-core/internal/research/text.go

89 lines
2.0 KiB
Go

package research
import (
"database/sql"
"errors"
"strings"
)
// Matches Paper Atlas repair 4e62710's nul-soh-v1 protocol: NUL -> SOH/SOH and literal
// SOH -> SOH/STX. All other bytes retain their original binary ordering.
const textCodec = "nul-soh-v1"
func encodedSchema(annotation sql.NullString) (bool, error) {
const prefix = "paper-atlas:text-codec="
if !annotation.Valid || !strings.HasPrefix(annotation.String, prefix) {
return false, nil
}
if strings.TrimPrefix(annotation.String, prefix) != textCodec {
return false, errors.New("unsupported research text codec")
}
return true, nil
}
func encodeText(value string) string {
return strings.ReplaceAll(strings.ReplaceAll(value, "\x01", "\x01\x02"), "\x00", "\x01\x01")
}
func decodeText(value string) (string, error) {
if !strings.ContainsRune(value, '\x01') {
return value, nil
}
var result strings.Builder
for {
position := strings.IndexByte(value, 1)
if position < 0 {
result.WriteString(value)
return result.String(), nil
}
result.WriteString(value[:position])
if position+1 >= len(value) {
return "", errors.New("invalid research text escape")
}
switch value[position+1] {
case 1:
result.WriteByte(0)
case 2:
result.WriteByte(1)
default:
return "", errors.New("invalid research text escape")
}
value = value[position+2:]
}
}
type textColumn struct {
encoded bool
target any
}
func (column textColumn) Scan(source any) error {
var value sql.NullString
if err := value.Scan(source); err != nil {
return err
}
if value.Valid && column.encoded {
var err error
value.String, err = decodeText(value.String)
if err != nil {
return err
}
}
switch target := column.target.(type) {
case *string:
if !value.Valid {
return errors.New("unexpected null research text")
}
*target = value.String
case *sql.NullString:
*target = value
default:
return errors.New("unsupported research text destination")
}
return nil
}
func (r *PostgresRepository) text(target any) sql.Scanner {
return textColumn{encoded: r.encoded, target: target}
}