diff --git a/macaddr.go b/macaddr.go new file mode 100644 index 00000000..2d09ff8c --- /dev/null +++ b/macaddr.go @@ -0,0 +1,154 @@ +package pgtype + +import ( + "database/sql/driver" + "fmt" + "io" + "net" +) + +type Macaddr struct { + Addr net.HardwareAddr + Status Status +} + +func (dst *Macaddr) Set(src interface{}) error { + if src == nil { + *dst = Macaddr{Status: Null} + return nil + } + + switch value := src.(type) { + case net.HardwareAddr: + addr := make(net.HardwareAddr, len(value)) + copy(addr, value) + *dst = Macaddr{Addr: addr, Status: Present} + case string: + addr, err := net.ParseMAC(value) + if err != nil { + return err + } + *dst = Macaddr{Addr: addr, Status: Present} + default: + if originalSrc, ok := underlyingPtrType(src); ok { + return dst.Set(originalSrc) + } + return fmt.Errorf("cannot convert %v to Macaddr", value) + } + + return nil +} + +func (dst *Macaddr) Get() interface{} { + switch dst.Status { + case Present: + return dst.Addr + case Null: + return nil + default: + return dst.Status + } +} + +func (src *Macaddr) AssignTo(dst interface{}) error { + switch src.Status { + case Present: + switch v := dst.(type) { + case *net.HardwareAddr: + *v = make(net.HardwareAddr, len(src.Addr)) + copy(*v, src.Addr) + return nil + case *string: + *v = src.Addr.String() + return nil + default: + if nextDst, retry := GetAssignToDstType(dst); retry { + return src.AssignTo(nextDst) + } + } + case Null: + return nullAssignTo(dst) + } + + return fmt.Errorf("cannot decode %v into %T", src, dst) +} + +func (dst *Macaddr) DecodeText(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = Macaddr{Status: Null} + return nil + } + + addr, err := net.ParseMAC(string(src)) + if err != nil { + return err + } + + *dst = Macaddr{Addr: addr, Status: Present} + return nil +} + +func (dst *Macaddr) DecodeBinary(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = Macaddr{Status: Null} + return nil + } + + if len(src) != 6 { + return fmt.Errorf("Received an invalid size for a macaddr: %d", len(src)) + } + + addr := make(net.HardwareAddr, 6) + copy(addr, src) + + *dst = Macaddr{Addr: addr, Status: Present} + + return nil +} + +func (src Macaddr) EncodeText(ci *ConnInfo, w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined + } + + _, err := io.WriteString(w, src.Addr.String()) + return false, err +} + +// EncodeBinary encodes src into w. +func (src Macaddr) EncodeBinary(ci *ConnInfo, w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined + } + + _, err := w.Write([]byte(src.Addr)) + return false, err +} + +// Scan implements the database/sql Scanner interface. +func (dst *Macaddr) Scan(src interface{}) error { + if src == nil { + *dst = Macaddr{Status: Null} + return nil + } + + switch src := src.(type) { + case string: + return dst.DecodeText(nil, []byte(src)) + case []byte: + return dst.DecodeText(nil, src) + } + + return fmt.Errorf("cannot scan %T", src) +} + +// Value implements the database/sql/driver Valuer interface. +func (src Macaddr) Value() (driver.Value, error) { + return encodeValueText(src) +} diff --git a/macaddr_test.go b/macaddr_test.go new file mode 100644 index 00000000..6c7b8b89 --- /dev/null +++ b/macaddr_test.go @@ -0,0 +1,77 @@ +package pgtype_test + +import ( + "bytes" + "net" + "reflect" + "testing" + + "github.com/jackc/pgx/pgtype" +) + +func TestMacaddrTranscode(t *testing.T) { + testSuccessfulTranscode(t, "macaddr", []interface{}{ + pgtype.Macaddr{Addr: mustParseMacaddr(t, "01:23:45:67:89:ab"), Status: pgtype.Present}, + pgtype.Macaddr{Status: pgtype.Null}, + }) +} + +func TestMacaddrSet(t *testing.T) { + successfulTests := []struct { + source interface{} + result pgtype.Macaddr + }{ + { + source: mustParseMacaddr(t, "01:23:45:67:89:ab"), + result: pgtype.Macaddr{Addr: mustParseMacaddr(t, "01:23:45:67:89:ab"), Status: pgtype.Present}, + }, + { + source: "01:23:45:67:89:ab", + result: pgtype.Macaddr{Addr: mustParseMacaddr(t, "01:23:45:67:89:ab"), Status: pgtype.Present}, + }, + } + + for i, tt := range successfulTests { + var r pgtype.Macaddr + err := r.Set(tt.source) + if err != nil { + t.Errorf("%d: %v", i, err) + } + + if !reflect.DeepEqual(r, tt.result) { + t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r) + } + } +} + +func TestMacaddrAssignTo(t *testing.T) { + { + src := pgtype.Macaddr{Addr: mustParseMacaddr(t, "01:23:45:67:89:ab"), Status: pgtype.Present} + var dst net.HardwareAddr + expected := mustParseMacaddr(t, "01:23:45:67:89:ab") + + err := src.AssignTo(&dst) + if err != nil { + t.Error(err) + } + + if bytes.Compare([]byte(dst), []byte(expected)) != 0 { + t.Errorf("expected %v to assign %v, but result was %v", src, expected, dst) + } + } + + { + src := pgtype.Macaddr{Addr: mustParseMacaddr(t, "01:23:45:67:89:ab"), Status: pgtype.Present} + var dst string + expected := "01:23:45:67:89:ab" + + err := src.AssignTo(&dst) + if err != nil { + t.Error(err) + } + + if dst != expected { + t.Errorf("expected %v to assign %v, but result was %v", src, expected, dst) + } + } +} diff --git a/pgtype.go b/pgtype.go index 52cad561..6b06539b 100644 --- a/pgtype.go +++ b/pgtype.go @@ -245,6 +245,7 @@ func init() { "jsonb": &Jsonb{}, "line": &Line{}, "lseg": &Lseg{}, + "macaddr": &Macaddr{}, "name": &Name{}, "numeric": &Numeric{}, "numrange": &Numrange{}, diff --git a/pgtype_test.go b/pgtype_test.go index 298cff64..0b1ffc54 100644 --- a/pgtype_test.go +++ b/pgtype_test.go @@ -78,6 +78,15 @@ func mustParseCidr(t testing.TB, s string) *net.IPNet { return ipnet } +func mustParseMacaddr(t testing.TB, s string) net.HardwareAddr { + addr, err := net.ParseMAC(s) + if err != nil { + t.Fatal(err) + } + + return addr +} + type forceTextEncoder struct { e pgtype.TextEncoder }