canopy-network/canopy · error · ErrInvalidPluginSchema
ErrInvalidPluginSchema
ErrInvalidPluginSchema
Error message
empty message name in type url %q
What it means
Register validates a plugin codec config's TransactionTypeUrls. Each URL is stripped to its last path segment; if nothing remains after the '/' (or the URL was empty), the derived proto message name is empty and registration fails with ErrInvalidPluginSchema.
Source
Thrown at lib/codec.go:150
// Unmarshal the file protos into a 'proto.Files' object
files, err := protodesc.NewFiles(&descriptorpb.FileDescriptorSet{File: fileProtos})
if err != nil {
return ErrInvalidPluginSchema(err)
}
byFullyQualifiedName := make(map[string]protoreflect.MessageDescriptor)
byTypeURL := make(map[string]protoreflect.MessageDescriptor)
byCommonMessageName := make(map[string]protoreflect.MessageDescriptor)
// for each transaction type URL - register it
for _, typeURL := range config.TransactionTypeUrls {
name := typeURL
if idx := strings.LastIndex(typeURL, "/"); idx >= 0 {
name = typeURL[idx+1:]
}
if name == "" {
return ErrInvalidPluginSchema(fmt.Errorf("empty message name in type url %q", typeURL))
}
desc, e := files.FindDescriptorByName(protoreflect.FullName(name))
if e != nil {
return ErrInvalidPluginSchema(fmt.Errorf("message %s: %w", name, e))
}
md, ok := desc.(protoreflect.MessageDescriptor)
if !ok {
return ErrInvalidPluginSchema(fmt.Errorf("descriptor %s is not a message", name))
}
byFullyQualifiedName[name] = md
byTypeURL[typeURL] = md
}
// for each transaction (message) common name - register it
if len(config.SupportedTransactions) > 0 {
if len(config.SupportedTransactions) != len(config.TransactionTypeUrls) {
return ErrInvalidPluginSchema(fmt.Errorf("supported transactions count %d does not match transaction type urls count %d", len(config.SupportedTransactions), len(config.TransactionTypeUrls)))
}View on GitHub (pinned to ee8197d91d)
Solutions
- Set a full type URL ending in a non-empty message name, e.g. "type.googleapis.com/myplugin.v1.Send"
- Remove the trailing '/' or empty entries from config.TransactionTypeUrls
- Validate the plugin config before loading it into the codec
Example fix
// before "transactionTypeUrls": ["type.googleapis.com/"] // after "transactionTypeUrls": ["type.googleapis.com/myplugin.v1.Send"]
Defensive patterns
Strategy: validation
Validate before calling
for _, u := range cfg.TransactionTypeUrls {
name := u[strings.LastIndex(u, "/")+1:]
if name == "" { return fmt.Errorf("bad type url %q", u) }
} Type guard
func validTypeURL(u string) bool {
i := strings.LastIndex(u, "/")
return i >= 0 && i < len(u)-1
} Try / catch
if err := codec.Register(cfg); err != nil {
var schemaErr *fsm.ErrInvalidPluginSchema
if errors.As(err, &schemaErr) { /* fail plugin load with clear config error */ }
return err
} Prevention
- Validate type URLs end with a message name before writing plugin config
- Avoid trailing slashes in type URLs
- Lint plugin configs at startup
When it happens
Trigger: Plugin config lists a TransactionTypeUrl that is empty ("") or ends with '/' so the trailing name after LastIndex("/") is empty.
Common situations: Hand-edited plugin config with a typo like "type.googleapis.com/" ; templated config where the message name variable was empty; truncated URL from an env var or CLI flag.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
AI-assisted analysis of canopy-network/canopy@ee8197d91d (2026-09-06).
Data as JSON: /api/errors/d5f89449e377c64d.
Report an issue: GitHub.