From 456604420ce55bde7f7244d948028830e48d71e5 Mon Sep 17 00:00:00 2001 From: Doria Keung Date: Tue, 15 Jul 2025 14:53:10 -0400 Subject: [PATCH 1/7] Separate protos into modules and add buf-action workflow --- .github/workflows/buf.yaml | 18 ++++++++++++++++++ Makefile | 7 +++---- buf.gen.vt.yaml | 6 ++++++ buf.gen.yaml | 4 +++- buf.yaml | 7 +++++-- .../{ => example}/weather/v1/weather.proto | 0 internal/proto/rsb/{ => rsb}/log/log.proto | 0 internal/proto/rsb/{ => rsb}/mesh/mesh.proto | 0 .../rsb/{ => rsb}/minecraft/minecraft.proto | 0 internal/proto/rsb/{ => rsb}/mk48/mk48.proto | 0 internal/proto/rsb/{ => rsb}/options.proto | 0 .../proto/test/{ => test}/descriptor.proto | 0 internal/proto/test/{ => test}/editions.proto | 0 internal/proto/test/{ => test}/proto2.proto | 0 internal/proto/test/{ => test}/test.proto | 0 15 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/buf.yaml rename internal/proto/example/{ => example}/weather/v1/weather.proto (100%) rename internal/proto/rsb/{ => rsb}/log/log.proto (100%) rename internal/proto/rsb/{ => rsb}/mesh/mesh.proto (100%) rename internal/proto/rsb/{ => rsb}/minecraft/minecraft.proto (100%) rename internal/proto/rsb/{ => rsb}/mk48/mk48.proto (100%) rename internal/proto/rsb/{ => rsb}/options.proto (100%) rename internal/proto/test/{ => test}/descriptor.proto (100%) rename internal/proto/test/{ => test}/editions.proto (100%) rename internal/proto/test/{ => test}/proto2.proto (100%) rename internal/proto/test/{ => test}/test.proto (100%) diff --git a/.github/workflows/buf.yaml b/.github/workflows/buf.yaml new file mode 100644 index 0000000..6b852d4 --- /dev/null +++ b/.github/workflows/buf.yaml @@ -0,0 +1,18 @@ +name: Buf CI +on: + push: + pull_request: + types: [opened, synchronize, reopened, labeled, unlabeled] + delete: +permissions: + contents: read + pull-requests: write +jobs: + buf: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: bufbuild/buf-action@v1 + with: + version: 1.50.0 # Keep in sync with Makefile BUF_VERSION + token: ${{ secrets.BUF_TOKEN }} diff --git a/Makefile b/Makefile index bf2ca66..fdba8f6 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ COPYRIGHT_YEARS := 2025 LICENSE_IGNORE := testdata/ GO_VERSION := go1.24.4 -BUF_VERSION := v1.50.0 +BUF_VERSION := v1.50.0 # Keep in sync w/ .github/workflows/buf.yaml. LINT_VERSION := v2.1.6 # Keep in sync w/ .github/workflows/ci.yaml. GOOS_HOST := $(shell go env GOOS) @@ -152,10 +152,9 @@ checkgenerate: @# Used in CI to verify that `make generate` doesn't produce a diff. git --no-pager diff --exit-code >&2 -internal/gen/*/*.pb.go: $(BIN)/buf internal/proto/*/*.proto internal/proto/*/*/*.proto +internal/gen/*/*.pb.go: $(BIN)/buf internal/proto/*/*/*.proto internal/proto/*/*/*/*.proto $(BIN)/buf generate --clean - $(BIN)/buf generate --template buf.gen.vt.yaml \ - --exclude-path internal/proto/test/proto2.proto,internal/proto/test/editions.proto # Work around a bug. + $(BIN)/buf generate --template buf.gen.vt.yaml .PHONY: $(BIN)/hypertest $(BIN)/hypertest: generate diff --git a/buf.gen.vt.yaml b/buf.gen.vt.yaml index cc2d68b..8ae86c5 100644 --- a/buf.gen.vt.yaml +++ b/buf.gen.vt.yaml @@ -8,3 +8,9 @@ plugins: - remote: buf.build/community/planetscale-vtprotobuf out: internal/gen opt: paths=source_relative +inputs: + - directory: . + # Work around a bug + exclude_paths: + - internal/proto/test/test/proto2.proto + - internal/proto/test/test/editions.proto diff --git a/buf.gen.yaml b/buf.gen.yaml index b84c0f3..ac748e3 100644 --- a/buf.gen.yaml +++ b/buf.gen.yaml @@ -10,4 +10,6 @@ managed: plugins: - remote: buf.build/protocolbuffers/go out: internal/gen - opt: paths=source_relative \ No newline at end of file + opt: paths=source_relative +inputs: + - directory: . \ No newline at end of file diff --git a/buf.yaml b/buf.yaml index 40f77f5..3ae1178 100644 --- a/buf.yaml +++ b/buf.yaml @@ -1,13 +1,16 @@ version: v2 modules: - - path: internal/proto + - path: internal/proto/example + name: buf.build/bufbuild/hyperpb-examples + - path: internal/proto/test + - path: internal/proto/rsb deps: - buf.build/bufbuild/protovalidate:v0.13.0 lint: use: - STANDARD ignore: - - internal/proto/test/descriptor.proto + - internal/proto/test/test/descriptor.proto breaking: use: - WIRE_JSON diff --git a/internal/proto/example/weather/v1/weather.proto b/internal/proto/example/example/weather/v1/weather.proto similarity index 100% rename from internal/proto/example/weather/v1/weather.proto rename to internal/proto/example/example/weather/v1/weather.proto diff --git a/internal/proto/rsb/log/log.proto b/internal/proto/rsb/rsb/log/log.proto similarity index 100% rename from internal/proto/rsb/log/log.proto rename to internal/proto/rsb/rsb/log/log.proto diff --git a/internal/proto/rsb/mesh/mesh.proto b/internal/proto/rsb/rsb/mesh/mesh.proto similarity index 100% rename from internal/proto/rsb/mesh/mesh.proto rename to internal/proto/rsb/rsb/mesh/mesh.proto diff --git a/internal/proto/rsb/minecraft/minecraft.proto b/internal/proto/rsb/rsb/minecraft/minecraft.proto similarity index 100% rename from internal/proto/rsb/minecraft/minecraft.proto rename to internal/proto/rsb/rsb/minecraft/minecraft.proto diff --git a/internal/proto/rsb/mk48/mk48.proto b/internal/proto/rsb/rsb/mk48/mk48.proto similarity index 100% rename from internal/proto/rsb/mk48/mk48.proto rename to internal/proto/rsb/rsb/mk48/mk48.proto diff --git a/internal/proto/rsb/options.proto b/internal/proto/rsb/rsb/options.proto similarity index 100% rename from internal/proto/rsb/options.proto rename to internal/proto/rsb/rsb/options.proto diff --git a/internal/proto/test/descriptor.proto b/internal/proto/test/test/descriptor.proto similarity index 100% rename from internal/proto/test/descriptor.proto rename to internal/proto/test/test/descriptor.proto diff --git a/internal/proto/test/editions.proto b/internal/proto/test/test/editions.proto similarity index 100% rename from internal/proto/test/editions.proto rename to internal/proto/test/test/editions.proto diff --git a/internal/proto/test/proto2.proto b/internal/proto/test/test/proto2.proto similarity index 100% rename from internal/proto/test/proto2.proto rename to internal/proto/test/test/proto2.proto diff --git a/internal/proto/test/test.proto b/internal/proto/test/test/test.proto similarity index 100% rename from internal/proto/test/test.proto rename to internal/proto/test/test/test.proto From 75048fa2f07bb4b8d48da5dc0557d1b16b3450f5 Mon Sep 17 00:00:00 2001 From: Doria Keung Date: Tue, 15 Jul 2025 17:06:14 -0400 Subject: [PATCH 2/7] Tweak README examples --- README.md | 197 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 103 insertions(+), 94 deletions(-) diff --git a/README.md b/README.md index 0020720..5f609af 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -![The Buf logo](./.github/buf-logo.svg) +![The Buf logo](https://raw.githubusercontent.com/bufbuild/hyperpb-go/main/.github/buf-logo.svg) # hyperpb `hyperpb` is a highly optimized dynamic message library for Protobuf or read-only workloads. It is designed to be a drop-in replacement for -[`dynamicpb`](https://pkg.go.dev/google.golang.org/protobuf/types/dynamicpb), +[`dynamicpb`][dynamicpb], `protobuf-go`'s canonical solution for working with completely dynamic messages. `hyperpb`'s parser is an efficient VM for a special instruction set, a variant of -table-driven parsing (TDP), pioneered by [the UPB project](https://github.com/protocolbuffers/protobuf/tree/main/upb). +table-driven parsing (TDP), pioneered by [the UPB project][upb]. Our parser is very fast, beating `dynamicpb` by 10x, and often beating `protobuf-go`'s generated code by a factor of 2-3x, especially for workloads with @@ -16,6 +16,8 @@ many nested messages. + + ## Usage The core conceit of `hyperpb` is that you must pre-compile a parser using @@ -29,15 +31,20 @@ our binary, and parse some data with it. ```go package main + import ( "fmt" + "log" "buf.build/go/hyperpb" "google.golang.org/protobuf/proto" - weatherv1 "github.com/..." + weatherv1 "buf.build/go/hyperpb/internal/gen/example/weather/v1" ) +// Weather data byte slice. +var weatherDataBytes = []byte{0xa, 0x7, 0x53, 0x65, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x12, 0x1d, 0xa, 0x5, 0x4b, 0x41, 0x44, 0x39, 0x33, 0x15, 0x66, 0x86, 0x22, 0x43, 0x1d, 0xcd, 0xcc, 0x34, 0x41, 0x25, 0xd7, 0xa3, 0xf0, 0x41, 0x2d, 0x33, 0x33, 0x13, 0x40, 0x30, 0x3, 0x12, 0x1d, 0xa, 0x5, 0x4b, 0x48, 0x42, 0x36, 0x30, 0x15, 0xcd, 0x8c, 0x22, 0x43, 0x1d, 0x33, 0x33, 0x5b, 0x41, 0x25, 0x52, 0xb8, 0xe0, 0x41, 0x2d, 0x33, 0x33, 0xf3, 0x3f, 0x30, 0x3} + func main() { // Compile a type for your message. Make sure to cache this! // Here, we're using a compiled-in descriptor. @@ -45,14 +52,13 @@ func main() { (*weatherv1.WeatherReport)(nil).ProtoReflect().Descriptor(), ) - data := /* ... */ - // Allocate a fresh message using that type. msg := hyperpb.NewMessage(msgType) // Parse the message, using proto.Unmarshal like any other message type. - if err := proto.Unmarshal(data, msg); err != nil { + if err := proto.Unmarshal(weatherDataBytes, msg); err != nil { // Handle parse failure. + log.Fatalf("failed to parse weather data: %v", err) } // Use reflection to read some fields. hyperpb currently only supports access @@ -95,23 +101,23 @@ is included in the documentation. ### Using types from a registry -We can use the `hyperpb.CompileForBytes` function to parse a dynamic type and +We can use the `hyperpb.CompileFileDescriptorSet` function to parse a dynamic type and use it to walk the fields of a message: ```go -package main - -import ( - "buf.build/go/hyperpb" - "google.golang.org/protobuf/proto" -) - -func main() { - msgType := hyperpb.CompileFileDescriptorSet(schema, messageName) // Remember to cache this! +func processDynamicMessage( + schema *descriptorpb.FileDescriptorSet, + messageName protoreflect.FullName, + data []byte, +) error { + msgType, err := hyperpb.CompileFileDescriptorSet(schema, messageName) // Remember to cache this! + if err != nil { + return err + } msg := hyperpb.NewMessage(msgType) if err := proto.Unmarshal(data, msg); err != nil { - // Handle parse failure. + return err } // Range will iterate over all of the populated fields in msg. Here we @@ -119,6 +125,7 @@ func main() { for field, value := range msg.Range { // Do something with each populated field. } + return nil } ``` @@ -127,50 +134,47 @@ we can use them as an efficient transcoding medium from the wire format, for runtime-loaded messages. ```go -package main - -import ( - "buf.build/go/hyperpb" - "google.golang.org/protobuf/encoding/protojson" - "google.golang.org/protobuf/proto" -) +func dynamicMessageToJSON( + schema *descriptorpb.FileDescriptorSet, + messageName protoreflect.FullName, + data []byte, +) ([]byte, error) { + msgType, err := hyperpb.CompileFileDescriptorSet(schema, messageName) + if err != nil { + return nil, err + } -func main() { - // Unmarshal like before. - msgType := hyperpb.CompileFileDescriptorSet(schema, messageName) msg := hyperpb.NewMessage(msgType) if err := proto.Unmarshal(data, msg); err != nil { - // ... + return nil, err } // Dump the message to JSON. This just works! - bytes, err := protojson.Marshal(msg) + return protojson.Marshal(msg) } ``` `protovalidate` also works directly on reflection, so it works out-of-the-box: ```go -package main - -import ( - "buf.build/go/hyperpb" - "buf.build/go/protovalidate" - "google.golang.org/protobuf/proto" -) - -func main() { +func validateDynamicMessage( + schema *descriptorpb.FileDescriptorSet, + messageName protoreflect.FullName, + data []byte, +) error { // Unmarshal like before. - msgType := hyperpb.CompileFileDescriptorSet(schema, messageName) + msgType, err := hyperpb.CompileFileDescriptorSet(schema, messageName) + if err != nil { + return err + } + msg := hyperpb.NewMessage(msgType) if err := proto.Unmarshal(data, msg); err != nil { - // Handle parse failure. + return err } // Run custom validation. This just works! - if err := protovalidate.Validate(msg); err != nil { - // Handle validation failure. - } + return protovalidate.Validate(msg) } ``` @@ -181,21 +185,21 @@ optimization knobs available. Calling `Message.Unmarshal` directly instead of `proto.Unmarshal` allows setting custom `UnmarshalOption`s: ```go -package main - -import ( - "buf.build/go/hyperpb" - "google.golang.org/protobuf/proto" -) +func unmarshalWithCustomOptions( + schema *descriptorpb.FileDescriptorSet, + messageName protoreflect.FullName, + data []byte, +) error { + msgType, err := hyperpb.CompileFileDescriptorSet(schema, messageName) + if err != nil { + return err + } -func main() { - msgType := hyperpb.CompileFileDescriptorSet(schema, messageName) msg := hyperpb.NewMessage(msgType) - - // Unmarshal with custom performance knobs. - err := msg.Unmarshal(data, + return msg.Unmarshal( + data, hyperpb.WithMaxDecodeMisses(16), - // ... + // Additional options... ) } ``` @@ -204,8 +208,11 @@ The compiler also takes `CompileOptions`, such as for configuring how extensions are resolved: ```go -msgType := hyperpb.CompileFileDescriptorSet(schema, messageName, +msgType, err := hyperpb.CompileFileDescriptor( + schema, + mesasgeName, hyperpb.WithExtensionsFromTypes(typeRegistry), + // Additional options... ) ``` @@ -224,11 +231,10 @@ away, allowing for re-use. Consider the following example of a request handler: type requestContext struct { shared *hyperpb.Shared types map[string]*hyperpb.MessageType - // ... + // Additional context fields... } func (c *requestContext) Handle(req Request) { - // ... msgType := c.types[req.Type] msg := c.shared.NewMessage(msgType) defer c.shared.Free() @@ -252,17 +258,12 @@ can build an optimized type, using that corpus as the profile, using `Type.Recompile`: ```go -package pgo - -import ( - "buf.build/go/hyperpb" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" -) - -func compilePGO(md protoreflect.MessageDescriptor, corpus [][]byte) *hyperpb.MessageType { +func compilePGO( + md protoreflect.MessageDescriptor, + corpus [][]byte, +) (*hyperpb.MessageType, error) { // Compile the type without any profiling information. - msgType := hyperpb.CompileForDescriptor(md) + msgType := hyperpb.CompileMessageDescriptor(md) // Construct a new profile recorder. profile := msgType.NewProfile() @@ -271,65 +272,69 @@ func compilePGO(md protoreflect.MessageDescriptor, corpus [][]byte) *hyperpb.Mes // for all of them. s := new(hyperpb.Shared) for _, specimen := range corpus { - s.NewMessage(msgType).Unmarshal(hyperpb.RecordProfile(profile, 1.0)) + if err := s.NewMessage(msgType).Unmarshal( + specimen, + hyperpb.WithRecordProfile(profile, 1.0), + ); err != nil { + return nil, err + } s.Free() } // Recompile with the profile. - return msgType.Recompile(profile) + return msgType.Recompile(profile), nil } ``` -Using a custom sampling rate in `hyperpb.RecordProfile`, it's possible to +Using a custom sampling rate in `hyperpb.WithRecordProfile`, it's possible to sample data on-line as part of a request flow, and recompile dynamically: ```go type requestContext struct { shared *hyperpb.Shared - types map[string]*hyperpb.Type - // ... + types map[string]*typeInfo + // Additional context fields... } type typeInfo struct { - msgType atomic.Pointer[hyperpb.Type] + msgType atomic.Pointer[hyperpb.MessageType] prof atomic.Pointer[hyperpb.Profile] seen atomic.Int64 } func (c *requestContext) Handle(req Request) { // Look up the type in the context's type map. - tyInfo := c.types[req.Type] - tyInfo.Lock() - + typeInfo := c.types[req.Type] + // Parse the type as usual. - msg := c.shared.NewMessage(tyInfo.msgType.Load()) + msg := c.shared.NewMessage(typeInfo.msgType.Load()) defer c.shared.Free() - err := msg.Unmarshal( + + if err := msg.Unmarshal( + data, // Only profile 1% of messages. - hyperpb.RecordProfile(tyInfo.prof.Load(), 0.01), - ) - if err != nil { - // ... + hyperpb.WithRecordProfile(typeInfo.prof.Load(), 0.01), + ); err != nil { + // Process error... } - tyInfo.seen.Add(1) + typeInfo.seen.Add(1) - // Every 100,000 messages, spawn a goroutine to asynchronously recompile - // the type. - if tyInfo.seen.Load() % 100000 == 0 { + // Every 100,000 messages, spawn a goroutine to asynchronously recompile the type. + if typeInfo.seen.Load() % 100000 == 0 { go func() { - prof := tyInfo.prof.Load() - if !tyInfo.CompareAndSwap(prof, nil) { + prof := typeInfo.prof.Load() + if !typeInfo.prof.CompareAndSwap(prof, nil) { // Avoid a race condition. return } // Recompile the type. This is gonna be really slow, because // the compiler is slow, which is why we're doing it asynchronously. - tyInfo.msgType.Store(tyInfo.msgType.Load().Recompile(tyInfo.prof)) - tyInfo.prof.Store(tyInfo.NewProfile()) + typeInfo.msgType.Store(typeInfo.msgType.Load().Recompile(typeInfo.prof.Load())) + typeInfo.prof.Store(typeInfo.msgType.Load().NewProfile()) } } - + // Do something with msg. } ``` @@ -348,4 +353,8 @@ parser will require benchmarks; you can run them with `make bench`. ## Legal -Offered under the [Apache 2 license](https://github.com/bufbuild/bufplugin-go/blob/main/LICENSE). \ No newline at end of file +Offered under the [Apache 2 license][license]. + +[dynamicpb]: https://pkg.go.dev/google.golang.org/protobuf/types/dynamicpb +[upb]: https://github.com/protocolbuffers/protobuf/tree/main/upb +[license]: https://github.com/bufbuild/hyperpb-go/blob/main/LICENSE \ No newline at end of file From c1accad2ffd00f12ded64a5ea8fde39c2b449c73 Mon Sep 17 00:00:00 2001 From: Doria Keung Date: Tue, 15 Jul 2025 17:19:45 -0400 Subject: [PATCH 3/7] Remove TODO --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 5f609af..7de7177 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,6 @@ many nested messages. - - ## Usage The core conceit of `hyperpb` is that you must pre-compile a parser using From 27e941b94d8811fdc56a746c238849bd1777b817 Mon Sep 17 00:00:00 2001 From: Doria Keung Date: Tue, 15 Jul 2025 17:28:05 -0400 Subject: [PATCH 4/7] Turn off format for buf-action --- .github/workflows/buf.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/buf.yaml b/.github/workflows/buf.yaml index 6b852d4..80d3ff7 100644 --- a/.github/workflows/buf.yaml +++ b/.github/workflows/buf.yaml @@ -15,4 +15,5 @@ jobs: - uses: bufbuild/buf-action@v1 with: version: 1.50.0 # Keep in sync with Makefile BUF_VERSION + format: false # Turn off format, since most proto definitions are for testing token: ${{ secrets.BUF_TOKEN }} From 0b5907dc96e9d39d3a622187b93692cf9010e884 Mon Sep 17 00:00:00 2001 From: Doria Keung Date: Tue, 15 Jul 2025 17:32:27 -0400 Subject: [PATCH 5/7] Turn off breaking temporarily --- .github/workflows/buf.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/buf.yaml b/.github/workflows/buf.yaml index 80d3ff7..595a559 100644 --- a/.github/workflows/buf.yaml +++ b/.github/workflows/buf.yaml @@ -16,4 +16,5 @@ jobs: with: version: 1.50.0 # Keep in sync with Makefile BUF_VERSION format: false # Turn off format, since most proto definitions are for testing + breaking: false # Temporarily turn-off breaking, since we are splitting up the modules token: ${{ secrets.BUF_TOKEN }} From 517beb06cbab7d29b9a952e98e5098a3ccb8c733 Mon Sep 17 00:00:00 2001 From: Doria Keung Date: Tue, 15 Jul 2025 17:33:09 -0400 Subject: [PATCH 6/7] Use skip breaking label instead --- .github/workflows/buf.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/buf.yaml b/.github/workflows/buf.yaml index 595a559..80d3ff7 100644 --- a/.github/workflows/buf.yaml +++ b/.github/workflows/buf.yaml @@ -16,5 +16,4 @@ jobs: with: version: 1.50.0 # Keep in sync with Makefile BUF_VERSION format: false # Turn off format, since most proto definitions are for testing - breaking: false # Temporarily turn-off breaking, since we are splitting up the modules token: ${{ secrets.BUF_TOKEN }} From 49b18887b23ddd7e86842f1c86b361cb3eb96e1d Mon Sep 17 00:00:00 2001 From: Doria Keung Date: Tue, 15 Jul 2025 18:10:50 -0400 Subject: [PATCH 7/7] Address comments --- README.md | 35 ++++++++++++++++++++++++++++++++--- example_test.go | 1 + 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7de7177..3541590 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ layout optimizations, withing making any source-breaking changes. For example, let's say that we want to compile a parser for some type baked into our binary, and parse some data with it. + + ```go package main @@ -37,11 +39,38 @@ import ( "buf.build/go/hyperpb" "google.golang.org/protobuf/proto" - weatherv1 "buf.build/go/hyperpb/internal/gen/example/weather/v1" + weatherv1 "buf.build/gen/go/bufbuild/hyperpb-examples/protocolbuffers/go/example/weather/v1" ) -// Weather data byte slice. -var weatherDataBytes = []byte{0xa, 0x7, 0x53, 0x65, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x12, 0x1d, 0xa, 0x5, 0x4b, 0x41, 0x44, 0x39, 0x33, 0x15, 0x66, 0x86, 0x22, 0x43, 0x1d, 0xcd, 0xcc, 0x34, 0x41, 0x25, 0xd7, 0xa3, 0xf0, 0x41, 0x2d, 0x33, 0x33, 0x13, 0x40, 0x30, 0x3, 0x12, 0x1d, 0xa, 0x5, 0x4b, 0x48, 0x42, 0x36, 0x30, 0x15, 0xcd, 0x8c, 0x22, 0x43, 0x1d, 0x33, 0x33, 0x5b, 0x41, 0x25, 0x52, 0xb8, 0xe0, 0x41, 0x2d, 0x33, 0x33, 0xf3, 0x3f, 0x30, 0x3} +// Byte slice representation of the following JSON data: +// +// { +// "region": "Seattle", +// "weatherStations": [ +// { +// "station": "KAD93", +// "frequency": 162.525, +// "temperature": 11.3, +// "pressure": 30.08, +// "windSpeed": 2.3, +// "conditions": "CONDITION_OVERCAST" +// }, +// { +// "station": "KHB60", +// "frequency": 162.55, +// "temperature": 13.7, +// "pressure": 28.09, +// "windSpeed": 1.9, +// "conditions": "CONDITION_OVERCAST" +// } +// ] +// } +var weatherDataBytes = []byte{0xa, 0x7, 0x53, 0x65, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x12, + 0x1d, 0xa, 0x5, 0x4b, 0x41, 0x44, 0x39, 0x33, 0x15, 0x66, 0x86, 0x22, 0x43, 0x1d, 0xcd, + 0xcc, 0x34, 0x41, 0x25, 0xd7, 0xa3, 0xf0, 0x41, 0x2d, 0x33, 0x33, 0x13, 0x40, 0x30, 0x3, + 0x12, 0x1d, 0xa, 0x5, 0x4b, 0x48, 0x42, 0x36, 0x30, 0x15, 0xcd, 0x8c, 0x22, 0x43, 0x1d, + 0x33, 0x33, 0x5b, 0x41, 0x25, 0x52, 0xb8, 0xe0, 0x41, 0x2d, 0x33, 0x33, 0xf3, 0x3f, + 0x30, 0x3} func main() { // Compile a type for your message. Make sure to cache this! diff --git a/example_test.go b/example_test.go index 49e0957..c560979 100644 --- a/example_test.go +++ b/example_test.go @@ -26,6 +26,7 @@ import ( weatherv1 "buf.build/go/hyperpb/internal/gen/example/weather/v1" ) +// Example data matches the data in the README example and should be kept in sync. func Example() { // Compile a type for your message. This operation is quite slow, so it // should be cached, like regexp.Compile.