Documentation
¶
Overview ¶
Package protofmt provides descriptor-aware PROTO and ENUM formatting plugins for spanvalue format configs.
The plugins in this package are opt-in. They are intended for display paths where protobuf descriptors are available, such as CLI table output. They do not change spanvalue preset defaults and do not replace descriptor-free SQL literal fallbacks such as github.com/apstndb/spanvalue.FormatProtoAsCast and github.com/apstndb/spanvalue.FormatEnumAsCast.
Generated protobuf types can be resolved through google.golang.org/protobuf/reflect/protoregistry.GlobalTypes when their generated packages are imported and linked into the binary, including by blank import.
To enable descriptor-aware display, clone an existing spanvalue format preset and prepend FormatProtoTextValue and FormatEnumNameValue before the preset's existing plugins. See the package examples for the minimal pattern.
Resolution failures fall through to wire forms by default (base64 for PROTO, numeric string for ENUM). Set ProtoTextValueOptions.OnUnresolved / EnumNameValueOptions.OnUnresolved to observe such fallthroughs when a resolver is configured, or to turn them into errors (strict mode).
Example (SpannerCLICompatibleFormatConfigWithProto) ¶
package main
import (
"fmt"
"github.com/apstndb/spanvalue"
"github.com/apstndb/spanvalue/gcvctor"
"github.com/apstndb/spanvalue/protofmt"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/descriptorpb"
)
func main() {
var fds *descriptorpb.FileDescriptorSet
// Load fds in the application, for example from a descriptor set produced
// with imports included. A nil fds is valid: it creates an empty resolver,
// so the plugins fall through to the preset's descriptor-free formatting.
dynamicResolver, err := protofmt.ProtoEnumResolverFromFileDescriptorSet(fds)
if err != nil {
panic(err)
}
resolver := protofmt.ComposeProtoEnumResolvers(
dynamicResolver,
protoregistry.GlobalTypes, // for generated protobuf packages linked into the binary
)
fc := spanvalue.SpannerCLICompatibleFormatConfig().Clone()
fc.FormatComplexPlugins = append(
[]spanvalue.FormatComplexFunc{
protofmt.FormatProtoTextValue(protofmt.ProtoTextValueOptions{Resolver: resolver}),
protofmt.FormatEnumNameValue(protofmt.EnumNameValueOptions{Resolver: resolver}),
},
fc.FormatComplexPlugins...,
)
out, err := fc.FormatToplevelColumn(gcvctor.EnumValue(
"google.protobuf.FieldDescriptorProto.Type",
int64(descriptorpb.FieldDescriptorProto_TYPE_STRING.Number()),
))
if err != nil {
panic(err)
}
fmt.Println(out)
}
Output: TYPE_STRING
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FormatEnumNameValue ¶
func FormatEnumNameValue(opts EnumNameValueOptions) spanvalue.FormatComplexFunc
FormatEnumNameValue returns a spanvalue plugin that formats Spanner ENUM values as enum value names when opts.Resolver can resolve the enum type and value number.
The plugin returns spanvalue.ErrFallthrough for non-ENUM values, nil or missing resolvers, empty type names, missing enum types, and typed NULL ENUM values (without consulting the resolver; the chain's built-in handling renders NULL via spanvalue.Formatter.GetNullString). Known enum types with unknown or out-of-range numeric values return the original numeric string.
EnumNameValueOptions.OnUnresolved optionally observes (and can turn into errors) the missing-enum-type fallthrough when a non-nil resolver is configured.
func FormatProtoTextValue ¶
func FormatProtoTextValue(opts ProtoTextValueOptions) spanvalue.FormatComplexFunc
FormatProtoTextValue returns a spanvalue plugin that formats Spanner PROTO values as protobuf text format when opts.Resolver can resolve the message type.
The plugin returns spanvalue.ErrFallthrough for non-PROTO values, nil or missing resolvers, empty type names, missing message types, and typed NULL PROTO values (without consulting the resolver; the chain's built-in handling renders NULL via spanvalue.Formatter.GetNullString). Malformed non-NULL wire payloads, base64 decode failures, unmarshal failures, and marshal failures are returned as real errors.
ProtoTextValueOptions.OnUnresolved optionally observes (and can turn into errors) the missing-message-type fallthrough when a non-nil resolver is configured.
Protobuf text output is display-oriented and intentionally not stable. Tests and callers must not depend on byte-for-byte stable output.
Types ¶
type EnumNameValueOptions ¶
type EnumNameValueOptions struct {
Resolver EnumResolver
// OnUnresolved, when non-nil, is invoked before falling through when
// Resolver is non-nil but cannot resolve the enum type of a non-NULL
// ENUM value with a non-empty type FQN (lookup returns exact
// [protoregistry.NotFound] or a nil type). If OnUnresolved returns a
// non-nil error, the plugin returns that error to the formatter caller;
// if it returns nil, the plugin falls through to wire-form output as
// usual. A nil OnUnresolved keeps the default lenient behavior.
//
// OnUnresolved is never invoked for nil resolvers, non-ENUM values,
// typed NULL values, empty type FQNs, or successful resolution. Returning
// an error from OnUnresolved is the strict-mode recipe; see
// [ProtoTextValueOptions.OnUnresolved] for the parallel PROTO option.
OnUnresolved func(typeFQN string, code sppb.TypeCode) error
}
EnumNameValueOptions configures FormatEnumNameValue.
type EnumResolver ¶
type EnumResolver interface {
FindEnumByName(protoreflect.FullName) (protoreflect.EnumType, error)
}
EnumResolver resolves protobuf enum types for descriptor-aware Spanner ENUM display.
type ProtoEnumResolver ¶
type ProtoEnumResolver interface {
protoregistry.MessageTypeResolver
protoregistry.ExtensionTypeResolver
EnumResolver
}
ProtoEnumResolver resolves protobuf message, extension, and enum types for descriptor-aware Spanner PROTO and ENUM display.
func ComposeProtoEnumResolvers ¶
func ComposeProtoEnumResolvers(resolvers ...ProtoEnumResolver) ProtoEnumResolver
ComposeProtoEnumResolvers returns a resolver that tries resolvers in order.
Nil resolvers are skipped. Lookup continues only when a resolver returns the exact protoregistry.NotFound sentinel; wrapped NotFound errors are returned as ordinary errors. If no resolver finds a type, the composed resolver returns exact protoregistry.NotFound.
func ProtoEnumResolverFromFileDescriptorSet ¶
func ProtoEnumResolverFromFileDescriptorSet(fds *descriptorpb.FileDescriptorSet) (ProtoEnumResolver, error)
ProtoEnumResolverFromFileDescriptorSet builds a dynamic protobuf resolver from fds.
A nil fds returns an empty resolver with nil error. Non-nil descriptor sets must be self-contained enough for protodesc.NewFiles to resolve imports. Reading .proto files, fetching remote descriptors, invoking compilers, and merging descriptor sets are application responsibilities.
type ProtoTextValueOptions ¶
type ProtoTextValueOptions struct {
Resolver ProtoEnumResolver
Unmarshal proto.UnmarshalOptions
Marshal prototext.MarshalOptions
// OnUnresolved, when non-nil, is invoked before falling through when
// Resolver is non-nil but cannot resolve the message type of a non-NULL
// PROTO value with a non-empty type FQN (lookup returns exact
// [protoregistry.NotFound] or a nil type). If OnUnresolved returns a
// non-nil error, the plugin returns that error to the formatter caller;
// if it returns nil, the plugin falls through to wire-form output as
// usual. A nil OnUnresolved keeps the default lenient behavior.
//
// OnUnresolved is never invoked for nil resolvers, non-PROTO values,
// typed NULL values, empty type FQNs, or successful resolution. Returning
// an error from OnUnresolved is the strict-mode recipe; see the example.
OnUnresolved func(typeFQN string, code sppb.TypeCode) error
}
ProtoTextValueOptions configures FormatProtoTextValue.
Resolver is authoritative: FormatProtoTextValue copies Unmarshal and Marshal, then overwrites both local Resolver fields from Resolver without mutating caller-owned options.
Example (OnUnresolved) ¶
Strict mode: when a resolver is configured but a non-NULL PROTO or ENUM value cannot be resolved, a one-line OnUnresolved handler turns the silent wire-form fallthrough into an error surfaced to the formatter caller.
package main
import (
"fmt"
sppb "cloud.google.com/go/spanner/apiv1/spannerpb"
"github.com/apstndb/spanvalue"
"github.com/apstndb/spanvalue/gcvctor"
"github.com/apstndb/spanvalue/protofmt"
)
func main() {
// An empty resolver stands in for a misconfigured descriptor set (for
// example a missing import or a type FQN typo).
resolver, err := protofmt.ProtoEnumResolverFromFileDescriptorSet(nil)
if err != nil {
panic(err)
}
strict := func(typeFQN string, code sppb.TypeCode) error {
return fmt.Errorf("protofmt: unresolved %v type %q", code, typeFQN)
}
fc := spanvalue.SpannerCLICompatibleFormatConfig().Clone()
fc.FormatComplexPlugins = append(
[]spanvalue.FormatComplexFunc{
protofmt.FormatProtoTextValue(protofmt.ProtoTextValueOptions{Resolver: resolver, OnUnresolved: strict}),
protofmt.FormatEnumNameValue(protofmt.EnumNameValueOptions{Resolver: resolver, OnUnresolved: strict}),
},
fc.FormatComplexPlugins...,
)
_, err = fc.FormatToplevelColumn(gcvctor.ProtoValue("example.music.SingerInfo", nil))
fmt.Println(err)
}
Output: protofmt: unresolved PROTO type "example.music.SingerInfo"