summaryrefslogtreecommitdiffstats
path: root/pkg/types/binary.go
blob: 00a5417f240d9476d0a33269ef4c204f274b955f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package types

import (
	"bytes"
	"database/sql"
	"database/sql/driver"
	"encoding"
	"encoding/hex"
	"encoding/json"
	"github.com/icinga/icingadb/internal"
	"github.com/icinga/icingadb/pkg/contracts"
	"github.com/pkg/errors"
)

// Binary nullable byte string. Hex as JSON.
type Binary []byte

// nullBinary for validating whether a Binary is valid.
var nullBinary Binary

// Equal returns whether the binaries are the same length and
// contain the same bytes.
func (binary Binary) Equal(equaler contracts.Equaler) bool {
	b, ok := equaler.(Binary)
	if !ok {
		panic("bad Binary type assertion")
	}

	return bytes.Equal(binary, b)
}

// Valid returns whether the Binary is valid.
func (binary Binary) Valid() bool {
	return !bytes.Equal(binary, nullBinary)
}

// String returns the hex string representation form of the Binary.
func (binary Binary) String() string {
	return hex.EncodeToString(binary)
}

// MarshalText implements a custom marhsal function to encode
// the Binary as hex. MarshalText implements the
// encoding.TextMarshaler interface.
func (binary Binary) MarshalText() ([]byte, error) {
	return []byte(binary.String()), nil
}

// UnmarshalText implements a custom unmarshal function to decode
// hex into a Binary. UnmarshalText implements the
// encoding.TextUnmarshaler interface.
func (binary *Binary) UnmarshalText(text []byte) error {
	b := make([]byte, hex.DecodedLen(len(text)))
	_, err := hex.Decode(b, text)
	if err != nil {
		return internal.CantDecodeHex(err, string(text))
	}
	*binary = b

	return nil
}

// MarshalJSON implements a custom marshal function to encode the Binary
// as a hex string. MarshalJSON implements the json.Marshaler interface.
// Supports JSON null.
func (binary Binary) MarshalJSON() ([]byte, error) {
	if !binary.Valid() {
		return nil, nil
	}

	return internal.MarshalJSON(binary.String())
}

// UnmarshalJSON implements a custom unmarshal function to decode
// a JSON hex string into a Binary. UnmarshalJSON implements the
// json.Unmarshaler interface. Supports JSON null.
func (binary *Binary) UnmarshalJSON(data []byte) error {
	if string(data) == "null" || len(data) == 0 {
		return nil
	}

	var s string
	if err := internal.UnmarshalJSON(data, &s); err != nil {
		return err
	}
	b, err := hex.DecodeString(s)
	if err != nil {
		return internal.CantDecodeHex(err, s)
	}
	*binary = b

	return nil
}

// Scan implements the sql.Scanner interface.
// Supports SQL NULL.
func (binary *Binary) Scan(src interface{}) error {
	switch src := src.(type) {
	case nil:
		return nil

	case []byte:
		if len(src) == 0 {
			return nil
		}

		b := make([]byte, len(src))
		copy(b, src)
		*binary = b

	default:
		return errors.Errorf("unable to scan type %T into Binary", src)
	}

	return nil
}

// Value implements the driver.Valuer interface.
// Supports SQL NULL.
func (binary Binary) Value() (driver.Value, error) {
	if !binary.Valid() {
		return nil, nil
	}

	return []byte(binary), nil
}

// Assert interface compliance.
var (
	_ contracts.ID             = (*Binary)(nil)
	_ encoding.TextMarshaler   = (*Binary)(nil)
	_ encoding.TextUnmarshaler = (*Binary)(nil)
	_ json.Marshaler           = (*Binary)(nil)
	_ json.Unmarshaler         = (*Binary)(nil)
	_ sql.Scanner              = (*Binary)(nil)
	_ driver.Valuer            = (*Binary)(nil)
)