Skip to content

Commit

Permalink
Merge pull request #23 from gkdr/use-scanner
Browse files Browse the repository at this point in the history
feat(option): if T is sql.Scanner, use it in Scan()
  • Loading branch information
samber authored Sep 25, 2023
2 parents 790425a + de406f7 commit d606129
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 1 deletion.
16 changes: 15 additions & 1 deletion option.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mo

import (
"bytes"
"database/sql"
"database/sql/driver"
"encoding/gob"
"encoding/json"
Expand Down Expand Up @@ -257,14 +258,27 @@ func (o *Option[T]) GobDecode(data []byte) error {
return o.UnmarshalBinary(data)
}

// Scan implements the SQL driver.Scanner interface.
// Scan implements the SQL sql.Scanner interface.
func (o *Option[T]) Scan(src any) error {
if src == nil {
o.isPresent = false
o.value = empty[T]()
return nil
}

// is is only possible to assert interfaces, so convert first
// https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#why-not-permit-type-assertions-on-values-whose-type-is-a-type-parameter
var t T
if tScanner, ok := interface{}(&t).(sql.Scanner); ok {
if err := tScanner.Scan(src); err != nil {
return fmt.Errorf("failed to scan: %w", err)
}

o.isPresent = true
o.value = t
return nil
}

if av, err := driver.DefaultParameterConverter.ConvertValue(src); err == nil {
if v, ok := av.(T); ok {
o.isPresent = true
Expand Down
40 changes: 40 additions & 0 deletions option_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mo
import (
"database/sql"
"encoding/json"
"fmt"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -413,3 +414,42 @@ func TestOptionValue(t *testing.T) {
is.EqualValues(None[string](), option2)
is.Nil(err2)
}

type SomeScanner struct {
Cool bool
Some int
}

func (ss *SomeScanner) Scan(src any) error {
val, ok := src.(string)
if !ok {
return fmt.Errorf("cannot scan - src is not a string")
}

var unmarshalled SomeScanner
if err := json.Unmarshal([]byte(val), &unmarshalled); err != nil {
return fmt.Errorf("failed to unmarshal json: %w", err)
}

*ss = unmarshalled
return nil
}

// If T is a sql.Scanner, make use of that.
func TestOptionScanner(t *testing.T) {
is := assert.New(t)

jsonString := `{"cool": true, "some": 123}`
nullString, _ := sql.NullString{}.Value()

var someScanner Option[SomeScanner]
var noneScanner Option[SomeScanner]

err1 := someScanner.Scan(jsonString)
err2 := noneScanner.Scan(nullString)

is.NoError(err1)
is.EqualValues(Some(SomeScanner{Cool: true, Some: 123}), someScanner)
is.NoError(err2)
is.EqualValues(None[SomeScanner](), noneScanner)
}

0 comments on commit d606129

Please sign in to comment.