wavetermdev/waveterm · error

Component function argument must be map[string]any, struct,

Error message

Component function argument must be map[string]any, struct, or any

What it means

The component function's single argument must be one of: map[string]any, a struct, a pointer to struct, or any (empty interface). Anything else — slices, primitives like string/int, channels, funcs — fails this final check with "Component function argument must be map[string]any, struct, or any".

Source

Thrown at tsunami/engine/rootelem.go:263

	if rtype.NumIn() != 1 {
		return fmt.Errorf("Component function must take exactly 1 argument")
	}
	if rtype.NumOut() != 1 {
		return fmt.Errorf("Component function must return exactly 1 value")
	}
	// first argument can be a map[string]any, or a struct, or ptr to struct (we'll reflect the value into it)
	arg1Type := rtype.In(0)
	if arg1Type.Kind() == reflect.Ptr {
		arg1Type = arg1Type.Elem()
	}
	if arg1Type.Kind() == reflect.Map {
		if arg1Type.Key().Kind() != reflect.String ||
			!(arg1Type.Elem().Kind() == reflect.Interface && arg1Type.Elem().NumMethod() == 0) {
			return fmt.Errorf("Map argument must be map[string]any")
		}
	} else if arg1Type.Kind() != reflect.Struct &&
		!(arg1Type.Kind() == reflect.Interface && arg1Type.NumMethod() == 0) {
		return fmt.Errorf("Component function argument must be map[string]any, struct, or any")
	}
	return nil
}

func (r *RootElem) RegisterComponent(name string, cfunc any) error {
	if err := validateCFunc(cfunc); err != nil {
		return err
	}
	r.CFuncs[name] = cfunc
	return nil
}

func callVDomFn(fnVal any, data vdom.VDomEvent) {
	if fnVal == nil {
		return
	}
	fn := fnVal
	if vdf, ok := fnVal.(*vdom.VDomFunc); ok {

View on GitHub (pinned to a4447c1563)

Solutions

  1. Change the parameter to map[string]any, a struct, *struct, or any
  2. Encode list-like props as fields inside a struct or as values in a map[string]any
  3. Wrap the existing function in a closure with an accepted parameter type

Example fix

// before
root.RegisterComponent("list", func(items []string) any { ... }) // slice not allowed
// after
type listProps struct { Items []string `json:"items"` }
root.RegisterComponent("list", func(p *listProps) any { return renderList(p.Items) })
Defensive patterns

Strategy: validation

Validate before calling

t := reflect.TypeOf(cfunc)
if t != nil && t.Kind() == reflect.Func && t.NumIn() == 1 {
    at := t.In(0)
    if at.Kind() == reflect.Ptr { at = at.Elem() }
    ok := at.Kind() == reflect.Map || at.Kind() == reflect.Struct ||
        (at.Kind() == reflect.Interface && at.NumMethod() == 0)
    if !ok { return fmt.Errorf("param type %v not allowed", t.In(0)) }
}

Type guard

func hasValidComponentParam(v any) bool {
    t := reflect.TypeOf(v)
    if t == nil || t.Kind() != reflect.Func || t.NumIn() != 1 { return false }
    at := t.In(0)
    if at.Kind() == reflect.Ptr { at = at.Elem() }
    if at.Kind() == reflect.Map { return at.Key().Kind() == reflect.String && at.Elem().Kind() == reflect.Interface }
    return at.Kind() == reflect.Struct || (at.Kind() == reflect.Interface && at.NumMethod() == 0)
}

Try / catch

if err := root.RegisterComponent(name, fn); err != nil {
    return fmt.Errorf("component %s param must be map[string]any/struct/any: %w", name, err)
}

Prevention

When it happens

Trigger: RegisterComponent with func(props []string) any; func(count int) any; func(cb func() string) any; any single-parameter function whose parameter is not a (pointer to) struct, map[string]any, or empty interface.

Common situations: Registering helper functions that take primitive options; expecting the engine to marshal arbitrary JSON types into slices; aliasing a parameter type that resolves to a slice or primitive.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/f849a4403eddabc5. Report an issue: GitHub.