@@ -2,13 +2,10 @@ package money
22
33import (
44 "database/sql/driver"
5- "errors"
65 "fmt"
7- "strings "
6+ "strconv "
87)
98
10- // DBAmount is a DB-facing wrapper for DECIMAL(10,2) columns.
11- // Use it in structs scanned from sql rows.
129type DBAmount struct {
1310 A Amount
1411}
@@ -19,100 +16,53 @@ func (m *DBAmount) Scan(value any) error {
1916 return nil
2017 }
2118
22- var s string
2319 switch v := value .(type ) {
2420 case []byte :
25- s = string (v )
26- case string :
27- s = v
28- default :
29- return fmt .Errorf ("money: unsupported scan type %T" , value )
30- }
31-
32- a , err := ParseString (s )
33- if err != nil {
34- return err
35- }
36- m .A = a
37- return nil
38- }
39-
40- func (m DBAmount ) Value () (driver.Value , error ) {
41- // write back as "12.34" for DECIMAL(10,2)
42- return m .A .StringFixed2 (), nil
43- }
21+ a , err := ParseString (string (v ))
22+ if err != nil {
23+ return err
24+ }
25+ m .A = a
26+ return nil
4427
45- // ParseString parses decimal string with optional sign, 2 decimals max.
46- // Accepts: "12.34", "12", "-0.50", " 12.30 "
47- func ParseString ( s string ) ( Amount , error ) {
48- s = strings . TrimSpace ( s )
49- if s == "" {
50- return 0 , errors . New ( "money: empty string" )
51- }
28+ case string :
29+ a , err := ParseString ( v )
30+ if err != nil {
31+ return err
32+ }
33+ m . A = a
34+ return nil
5235
53- sign := int64 (1 )
54- if s [0 ] == '-' {
55- sign = - 1
56- s = s [1 :]
57- } else if s [0 ] == '+' {
58- s = s [1 :]
59- }
36+ case float64 :
37+ // Force 2 decimals, then parse strictly.
38+ s := strconv .FormatFloat (v , 'f' , 2 , 64 )
39+ a , err := ParseString (s )
40+ if err != nil {
41+ return err
42+ }
43+ m .A = a
44+ return nil
6045
61- parts := strings .Split (s , "." )
62- if len (parts ) > 2 {
63- return 0 , fmt .Errorf ("money: invalid format: %q" , s )
64- }
46+ case float32 :
47+ s := strconv .FormatFloat (float64 (v ), 'f' , 2 , 64 )
48+ a , err := ParseString (s )
49+ if err != nil {
50+ return err
51+ }
52+ m .A = a
53+ return nil
6554
66- wholeStr := parts [0 ]
67- if wholeStr == "" {
68- wholeStr = "0"
69- }
70- var fracStr string
71- if len (parts ) == 2 {
72- fracStr = parts [1 ]
73- }
55+ case int64 :
56+ // If driver returns integer, assume it's already minor? (ambiguous)
57+ // Better to treat as major units without decimals is dangerous.
58+ // If you WANT to support this, define it clearly. For safety, reject:
59+ return fmt .Errorf ("money: unsupported scan int64=%d (ambiguous units)" , v )
7460
75- // normalize fraction to 2 digits
76- switch len (fracStr ) {
77- case 0 :
78- fracStr = "00"
79- case 1 :
80- fracStr = fracStr + "0"
81- case 2 :
82- // ok
8361 default :
84- // If more than 2 decimals exist, you can either reject or round.
85- // For strict DECIMAL(10,2), reject is safest.
86- return 0 , fmt .Errorf ("money: too many decimal places: %q" , s )
87- }
88-
89- whole , err := parseUint (wholeStr )
90- if err != nil {
91- return 0 , fmt .Errorf ("money: invalid whole part: %w" , err )
92- }
93- frac , err := parseUint (fracStr )
94- if err != nil {
95- return 0 , fmt .Errorf ("money: invalid fractional part: %w" , err )
96- }
97- if frac > 99 {
98- return 0 , fmt .Errorf ("money: invalid fractional part: %q" , fracStr )
62+ return fmt .Errorf ("money: unsupported scan type %T" , value )
9963 }
100-
101- minor := int64 (whole )* 100 + int64 (frac )
102- return Amount (sign * minor ), nil
10364}
10465
105- func parseUint (s string ) (uint64 , error ) {
106- if s == "" {
107- return 0 , errors .New ("empty" )
108- }
109- var n uint64
110- for i := 0 ; i < len (s ); i ++ {
111- c := s [i ]
112- if c < '0' || c > '9' {
113- return 0 , fmt .Errorf ("non-digit %q" , c )
114- }
115- n = n * 10 + uint64 (c - '0' )
116- }
117- return n , nil
66+ func (m DBAmount ) Value () (driver.Value , error ) {
67+ return m .A .StringFixed2 (), nil
11868}
0 commit comments