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

  1. Set a full type URL ending in a non-empty message name, e.g. "type.googleapis.com/myplugin.v1.Send"
  2. Remove the trailing '/' or empty entries from config.TransactionTypeUrls
  3. 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

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.