action API

action

package

API reference for the action package.

S
struct

Transport

Transport handles action-based routing for GUI/CLI applications.

pkg/transport/action/transport.go:16-23
type Transport struct

Methods

Provide
Method

Provide registers a dependency.

Parameters

name string
instance any
func (*Transport) Provide(name string, instance any)
{
	t.container.Provide(name, instance)
}
Register
Method

Register adds an action handler. Reads `action:"name"` and `keys:"ctrl+s"` tags from Pattern field.

Parameters

prototype core.Handler
func (*Transport) Register(prototype core.Handler)
{
	val := reflect.ValueOf(prototype)
	if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct {
		panic("Transport.Register: prototype must be a pointer to a struct")
	}

	elemType := val.Elem().Type()

	var actionName, keyBinding string
	for i := 0; i < elemType.NumField(); i++ {
		field := elemType.Field(i)
		if field.Type == reflect.TypeOf(core.Pattern{}) {
			actionName = field.Tag.Get("action")
			keyBinding = field.Tag.Get("keys")
			break
		}
	}

	if actionName == "" {
		panic(fmt.Sprintf("Transport.Register: struct %s missing Pattern with action tag", elemType.Name()))
	}

	t.mu.Lock()
	defer t.mu.Unlock()

	t.handlers[actionName] = prototype
	if keyBinding != "" {
		t.keys[keyBinding] = actionName
	}

	t.Logger.Info("Registered action", "action", actionName, "keys", keyBinding)
}
Dispatch
Method

Dispatch executes an action by name with an optional payload.

Parameters

action string
payload ...any

Returns

any
error
func (*Transport) Dispatch(ctx context.Context, action string, payload ...any) (any, error)
{
	t.mu.RLock()
	prototype, ok := t.handlers[action]
	busInstance := t.Bus
	t.mu.RUnlock()

	if !ok {
		return nil, fmt.Errorf("action not found: %s", action)
	}

	// Create new instance
	val := reflect.ValueOf(prototype)
	elemType := val.Elem().Type()
	newVal := reflect.New(elemType).Elem()
	newVal.Set(val.Elem())

	// Inject dependencies
	instance := newVal.Addr().Interface()
	t.container.Inject(instance)

	// Real payload binding
	if len(payload) > 0 && payload[0] != nil {
		if err := t.applyPayload(instance, payload[0]); err != nil {
			return nil, fmt.Errorf("payload binding failed: %w", err)
		}
	}

	// Execute
	handler := instance.(core.Handler)
	res, err := handler.Handle(ctx)

	// If a bus is present, emit the action as an event asynchronously
	if busInstance != nil {
		bus.EmitAsync(ctx, busInstance, instance)
	}

	return res, err
}
applyPayload
Method

applyPayload maps data from the payload to the target struct. It supports map[string]any and structs, using reflection and "json" tags.

Parameters

target any
payload any

Returns

error
func (*Transport) applyPayload(target any, payload any) error
{
	dstVal := reflect.ValueOf(target).Elem()
	dstType := dstVal.Type()

	srcVal := reflect.ValueOf(payload)
	if srcVal.Kind() == reflect.Ptr {
		srcVal = srcVal.Elem()
	}

	switch srcVal.Kind() {
	case reflect.Map:
		for _, key := range srcVal.MapKeys() {
			k := fmt.Sprintf("%v", key.Interface())
			v := srcVal.MapIndex(key)
			t.setFieldByNameOrTag(dstVal, dstType, k, v)
		}
	case reflect.Struct:
		srcType := srcVal.Type()
		for i := 0; i < srcVal.NumField(); i++ {
			field := srcType.Field(i)
			val := srcVal.Field(i)
			t.setFieldByNameOrTag(dstVal, dstType, field.Name, val)
		}
	}

	return nil
}

Parameters

dstType reflect.Type
name string
func (*Transport) setFieldByNameOrTag(dst reflect.Value, dstType reflect.Type, name string, val reflect.Value)
{
	for i := 0; i < dst.NumField(); i++ {
		fieldMeta := dstType.Field(i)
		field := dst.Field(i)

		if !field.CanSet() {
			continue
		}

		// Match by name or json tag
		tag := fieldMeta.Tag.Get("json")
		if strings.EqualFold(fieldMeta.Name, name) || (tag != "" && strings.Split(tag, ",")[0] == name) {
			if field.Type() == val.Type() {
				field.Set(val)
			} else if val.Kind() == reflect.Interface && reflect.TypeOf(val.Interface()) == field.Type() {
				field.Set(reflect.ValueOf(val.Interface()))
			}
			// Future: add type conversion (string to int, etc) if needed
			break
		}
	}
}
DispatchKey
Method

DispatchKey executes an action by keybinding.

Parameters

key string

Returns

any
error
func (*Transport) DispatchKey(ctx context.Context, key string) (any, error)
{
	t.mu.RLock()
	action, ok := t.keys[key]
	t.mu.RUnlock()

	if !ok {
		return nil, fmt.Errorf("no action bound to key: %s", key)
	}

	return t.Dispatch(ctx, action)
}
Actions
Method

Actions returns all registered action names.

Returns

[]string
func (*Transport) Actions() []string
{
	t.mu.RLock()
	defer t.mu.RUnlock()

	actions := make([]string, 0, len(t.handlers))
	for name := range t.handlers {
		actions = append(actions, name)
	}
	return actions
}
KeyBindings
Method

KeyBindings returns all registered keybindings.

Returns

map[string]string
func (*Transport) KeyBindings() map[string]string
{
	t.mu.RLock()
	defer t.mu.RUnlock()

	bindings := make(map[string]string, len(t.keys))
	for k, v := range t.keys {
		bindings[k] = v
	}
	return bindings
}

Fields

Name Type Description
container *core.Container
Logger logger.Logger
handlers map[string]core.Handler
keys map[string]string
Bus *bus.Bus
mu sync.RWMutex
T
type

Option

pkg/transport/action/transport.go:25-25
type Option func(*Transport)
F
function

WithBus

Parameters

b

Returns

pkg/transport/action/transport.go:27-29
func WithBus(b *bus.Bus) Option

{
	return func(t *Transport) { t.Bus = b }
}
F
function

New

New creates a new action transport.

Parameters

opts
...Option

Returns

pkg/transport/action/transport.go:32-44
func New(opts ...Option) *Transport

{
	t := &Transport{
		container: core.NewContainer(),
		Logger:    logger.Nop,
		handlers:  make(map[string]core.Handler),
		keys:      make(map[string]string),
		Bus:       bus.Default(),
	}
	for _, opt := range opts {
		opt(t)
	}
	return t
}