Skip to content

Commit

Permalink
money: implement sql driver methods and enable linters
Browse files Browse the repository at this point in the history
  • Loading branch information
eapenkin committed Aug 20, 2023
1 parent 9beff69 commit 3d412c2
Show file tree
Hide file tree
Showing 11 changed files with 159 additions and 44 deletions.
4 changes: 4 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ linters:
- goimports
- govet
- ineffassign
- godot
- gosec
- misspell
- stylecheck
- revive
- staticcheck
- typecheck
- unused
- gocyclo
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## [0.1.3] - 2023-08-21

### Added

- Implemented `Currency.Scan` and `Currency.Value`.

### Changed

- `Amount.CopySign` treats 0 as a positive.
- Enabled `gocyclo`, `gosec`, `godot`, and `stylecheck` linters.

## [0.1.2] - 2023-08-15

### Added
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
[![githubb]][github]
[![codecovb]][codecov]
[![goreportb]][goreport]
[![licenseb]][license]
[![godocb]][godoc]
[![licenseb]][license]
[![versionb]][version]

Package money implements immutable monetary amounts for Go.
Expand Down
33 changes: 28 additions & 5 deletions amount.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func (a Amount) Neg() Amount {
}

// CopySign returns the amount with the same sign as amount b.
// If amount b is zero, the sign of the result remains unchanged.
// Zero is always treated as positive.
func (a Amount) CopySign(b Amount) Amount {
d, e := a.value, b.value
return newAmountUnsafe(a.Curr(), d.CopySign(e))
Expand Down Expand Up @@ -572,7 +572,7 @@ func (a Amount) SameScale(b Amount) bool {
}

// SameScaleAsCurr returns true if the scale of the amount matches the scale of its currency.
// See also method [Amount.RoundToCurr].
// See also methods [Amount.Scale] and [Currency.Scale].
func (a Amount) SameScaleAsCurr() bool {
return a.Scale() == a.Curr().Scale()
}
Expand Down Expand Up @@ -667,6 +667,8 @@ func (a Amount) Max(b Amount) (Amount, error) {
//
// [format verbs]: https://pkg.go.dev/fmt#hdr-Printing
// [fmt.Formatter]: https://pkg.go.dev/fmt#Formatter
//
//gocyclo:ignore
func (a Amount) Format(state fmt.State, verb rune) {
// Rescaling
tzeroes := 0
Expand Down Expand Up @@ -732,13 +734,13 @@ func (a Amount) Format(state fmt.State, verb rune) {
}
currlen := len(curr)

// Quotes
// Opening and closing quotes
lquote, tquote := 0, 0
if verb == 'q' || verb == 'Q' {
lquote, tquote = 1, 1
}

// Padding
// Calculating padding
width := lquote + rsign + intdigs + dpoint + fracdigs + tzeroes + currlen + tquote
lspaces, lzeroes, tspaces := 0, 0, 0
if w, ok := state.Width(); ok && w > width {
Expand All @@ -753,40 +755,55 @@ func (a Amount) Format(state fmt.State, verb rune) {
width = w
}

// Writing buffer
buf := make([]byte, width)
pos := width - 1

// Trailing spaces
for i := 0; i < tspaces; i++ {
buf[pos] = ' '
pos--
}

// Closing quote
if tquote > 0 {
buf[pos] = '"'
pos--
}

// Trailing zeroes
for i := 0; i < tzeroes; i++ {
buf[pos] = '0'
pos--
}

// Fractional digits
coef := a.Coef()
for i := 0; i < fracdigs; i++ {
buf[pos] = byte(coef%10) + '0'
pos--
coef /= 10
}

// Decimal point
if dpoint > 0 {
buf[pos] = '.'
pos--
}

// Integer digits
for i := 0; i < intdigs; i++ {
buf[pos] = byte(coef%10) + '0'
pos--
coef /= 10
}

// Leading zeroes
for i := 0; i < lzeroes; i++ {
buf[pos] = '0'
pos--
}

// Arithmetic sign
if rsign > 0 {
if a.IsNeg() {
buf[pos] = '-'
Expand All @@ -797,14 +814,20 @@ func (a Amount) Format(state fmt.State, verb rune) {
}
pos--
}

// Currency symbols
for i := currlen; i > 0; i-- {
buf[pos] = curr[i-1]
pos--
}

// Opening quote
if lquote > 0 {
buf[pos] = '"'
pos--
}

// Leading spaces
for i := 0; i < lspaces; i++ {
buf[pos] = ' '
pos--
Expand Down
2 changes: 0 additions & 2 deletions amount_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,6 @@ func TestAmount_SameScaleAsCurr(t *testing.T) {
c, a string
want bool
}{
{"USD", "1", true},
{"USD", "1.0", true},
{"USD", "1.00", true},
{"USD", "1.000", false},
{"USD", "1.0000", false},
Expand Down
66 changes: 48 additions & 18 deletions currency.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package money

import (
"database/sql/driver"
"errors"
"fmt"
)
Expand All @@ -20,9 +21,7 @@ import (
// index and a particular currency may change in future versions.
type Currency uint8

var (
errUnknownCurrency = errors.New("unknown currency")
)
var errUnknownCurrency = errors.New("unknown currency")

// ParseCurr converts a string to currency.
// The input string must be in one of the following formats:
Expand Down Expand Up @@ -50,16 +49,6 @@ func MustParseCurr(curr string) Currency {
return c
}

// UnmarshalText implements [encoding.TextUnmarshaler] interface.
// Also see method [ParseCurr].
//
// [encoding.TextUnmarshaler]: https://pkg.go.dev/encoding#TextUnmarshaler
func (c *Currency) UnmarshalText(text []byte) error {
var err error
*c, err = ParseCurr(string(text))
return err
}

// Scale returns the number of digits after the decimal point required for
// the minor unit of the currency.
// This represents the [ratio] of the minor unit to the major unit.
Expand Down Expand Up @@ -96,6 +85,16 @@ func (c Currency) String() string {
return c.Code()
}

// UnmarshalText implements [encoding.TextUnmarshaler] interface.
// Also see method [ParseCurr].
//
// [encoding.TextUnmarshaler]: https://pkg.go.dev/encoding#TextUnmarshaler
func (c *Currency) UnmarshalText(text []byte) error {
var err error
*c, err = ParseCurr(string(text))
return err
}

// MarshalText implements [encoding.TextMarshaler] interface.
// Also see method [Currency.String].
//
Expand All @@ -104,7 +103,30 @@ func (c Currency) MarshalText() ([]byte, error) {
return []byte(c.String()), nil
}

// Format implements [fmt.Formatter] interface.
// Scan implements the [sql.Scanner] interface.
// See also method [ParseCurr].
//
// [sql.Scanner]: https://pkg.go.dev/database/sql#Scanner
func (c *Currency) Scan(v any) error {
var err error
switch v := v.(type) {
case string:
*c, err = ParseCurr(v)
default:
err = fmt.Errorf("failed to convert from %T to %T", v, XXX)
}
return err
}

// Value implements the [driver.Valuer] interface.
// See also method [Currency.String].
//
// [driver.Valuer]: https://pkg.go.dev/database/sql/driver#Valuer
func (c Currency) Value() (driver.Value, error) {
return c.String(), nil
}

// Format implements the [fmt.Formatter] interface.
// The following [verbs] are available:
//
// %s, %v: USD
Expand All @@ -116,18 +138,17 @@ func (c Currency) MarshalText() ([]byte, error) {
// [verbs]: https://pkg.go.dev/fmt#hdr-Printing
// [fmt.Formatter]: https://pkg.go.dev/fmt#Formatter
func (c Currency) Format(state fmt.State, verb rune) {

// Currency symbols
curr := c.Code()
currlen := len(curr)

// Quotes
// Opening and closing quotes
lquote, tquote := 0, 0
if verb == 'q' || verb == 'Q' {
lquote, tquote = 1, 1
}

// Padding
// Calculating padding
width := lquote + currlen + tquote
lspaces, tspaces := 0, 0
if w, ok := state.Width(); ok && w > width {
Expand All @@ -140,25 +161,34 @@ func (c Currency) Format(state fmt.State, verb rune) {
width = w
}

// Writing buffer
buf := make([]byte, width)
pos := width - 1

// Trailing spaces
for i := 0; i < tspaces; i++ {
buf[pos] = ' '
pos--
}

// Closing quote
if tquote > 0 {
buf[pos] = '"'
pos--
}

// Currency symbols
for i := currlen; i > 0; i-- {
buf[pos] = curr[i-1]
pos--
}

// Opening quote
if lquote > 0 {
buf[pos] = '"'
pos--
}

// Leading spaces
for i := 0; i < lspaces; i++ {
buf[pos] = ' '
pos--
Expand Down
15 changes: 12 additions & 3 deletions currency_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import (
)

func TestCurrency_Parse(t *testing.T) {

t.Run("valid", func(t *testing.T) {
t.Run("success", func(t *testing.T) {
tests := []struct {
code string
want Currency
Expand Down Expand Up @@ -37,7 +36,7 @@ func TestCurrency_Parse(t *testing.T) {
}
})

t.Run("invalid", func(t *testing.T) {
t.Run("error", func(t *testing.T) {
tests := []string{
"", "000", "test", "xbt", "$", "AU$", "BTC",
}
Expand Down Expand Up @@ -158,3 +157,13 @@ func TestCurrency_Format(t *testing.T) {
}
}
}

func TestCurrency_Scan(t *testing.T) {
t.Run("error", func(t *testing.T) {
c := XXX
err := c.Scan([]byte("USD"))
if err == nil {
t.Errorf("c.Scan([]byte(\"USD\")) did not fail")
}
})
}
Loading

0 comments on commit 3d412c2

Please sign in to comment.