Skip to content

Commit

Permalink
Tidy up options
Browse files Browse the repository at this point in the history
  • Loading branch information
pcj committed Jun 22, 2023
1 parent c7036ab commit df157e1
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 215 deletions.
2 changes: 0 additions & 2 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ go_library(
srcs = [
"hasher.go",
"hashing.go",
"protoreflecthash.go",
],
importpath = "github.com/stackb/protoreflecthash",
visibility = ["//visibility:public"],
Expand All @@ -26,7 +25,6 @@ go_test(
name = "protoreflecthash_test",
srcs = [
"hasher_test.go",
"protoreflecthash_test.go",
],
data = glob(["testdata/**"]),
embed = [":protoreflecthash"],
Expand Down
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ protoreflecthash is a re-implementation of
<https://github.com/deepmind/objecthash-proto>. That repo is now archived and
was never updated for [protobuf-apiv2](https://go.dev/blog/protobuf-apiv2).

This package is currently experimental; the hash values for messages will likely
change without warning until v1.
This package is currently experimental; the hash values for messages may change
without warning until v1.

# Usage

Expand All @@ -36,11 +36,22 @@ import (
func main() {
msg := mustGetProtoMessageSomewhere()

hex, err := protoreflecthash.String(msg)
hasher := protoreflect.NewHasher() // see options
hex, err := protoreflecthash.String(msg.ProtoReflect())
if err != nil {
panic(err.Error())
}

println(hex)
}
```

# Background

`protoreflecthash` computes the hash value for a protobuf message by taking a
sha256 of the sum the individual component hashes of the message.

This implementation passes all functional unit tests from the original library
[deepmind/objecthash-proto](https://github.com/deepmind/objecthash-proto)
(excluding badness detection).

40 changes: 32 additions & 8 deletions hasher.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,37 @@ import (

const valueName = protoreflect.Name("value")

type ProtoHasherOption func(*hasher)
// Option modifies how hashes for protobufs is calculated.
type Option func(*hasher)

// ProtoHasher is an interface for hashers that are capable of returning an
// ObjectHash for protobufs.
type ProtoHasher interface {
// HashProto returns the object hash of a given protocol buffer message.
HashProto(msg protoreflect.Message) ([]byte, error)
}

func NewHasher(options ...ProtoHasherOption) ProtoHasher {
return &hasher{}
// NewHasher creates a new ProtoHasher with the options specified in the
// argument.
func NewHasher(options ...Option) ProtoHasher {
h := &hasher{}
for _, opt := range options {
opt(h)
}
return h
}

func FieldNamesAsKeys() ProtoHasherOption {
// MessageFullnameIdentifier is an option that uses the message descriptor
// fullname rather than a generic map identifier when hashing messages.
func MessageFullnameIdentifier() Option {
return func(h *hasher) {
h.messageFullnameIdentifier = true
}
}

// FieldNamesAsKeys is an option that uses field names for key hashing rather
// than the field number.
func FieldNamesAsKeys() Option {
return func(h *hasher) {
h.fieldNamesAsKeys = true
}
Expand All @@ -31,6 +51,9 @@ type hasher struct {
// Whether to use the proto field name as its key, as opposed to using the
// tag number as the key.
fieldNamesAsKeys bool
// Whether to use the fullname of the message descriptor rather than 'm'
// (mapIdentifier) for proto messages.
messageFullnameIdentifier bool
}

type fieldHashEntry struct {
Expand Down Expand Up @@ -91,9 +114,10 @@ func (h *hasher) hashMessage(msg protoreflect.Message) ([]byte, error) {
}

identifier := mapIdentifier
// if hasher.messageIdentifier != "" {
// identifier = hasher.messageIdentifier
// }
if h.messageFullnameIdentifier {
identifier = string(md.FullName())
}

return hash(identifier, buf.Bytes())
}

Expand Down Expand Up @@ -328,7 +352,7 @@ func (h *hasher) hashGoogleProtobufAny(md protoreflect.MessageDescriptor, msg pr
// files := protoregistry.GlobalFiles - create option to set files explictly?
typeUrl := msg.Get(md.Fields().ByName("type_url")).String()
// TODO: lookup at a type server?
return nil, fmt.Errorf("unsupported type: " + typeUrl)
return nil, fmt.Errorf("protoreflecthash does not support hashing of Any type: " + typeUrl)
}

func (h *hasher) hashGoogleProtobufDuration(md protoreflect.MessageDescriptor, msg protoreflect.Message) ([]byte, error) {
Expand Down

0 comments on commit df157e1

Please sign in to comment.