2026-04-03 20:15:36 +00:00
|
|
|
package dns
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2026-04-03 23:00:07 +00:00
|
|
|
"encoding/json"
|
2026-04-03 20:15:36 +00:00
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
2026-04-03 23:00:07 +00:00
|
|
|
"math"
|
|
|
|
|
"strings"
|
2026-04-03 20:15:36 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
ActionResolve = "dns.resolve"
|
|
|
|
|
ActionResolveTXT = "dns.resolve.txt"
|
|
|
|
|
ActionResolveAll = "dns.resolve.all"
|
|
|
|
|
ActionReverse = "dns.reverse"
|
|
|
|
|
ActionServe = "dns.serve"
|
|
|
|
|
ActionHealth = "dns.health"
|
|
|
|
|
ActionDiscover = "dns.discover"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
errActionNotFound = errors.New("dns action not found")
|
|
|
|
|
errActionMissingValue = errors.New("dns action missing required value")
|
|
|
|
|
)
|
|
|
|
|
|
2026-04-03 23:00:07 +00:00
|
|
|
const (
|
2026-04-04 00:20:49 +00:00
|
|
|
actionArgBind = "bind"
|
|
|
|
|
actionArgBindAddress = "bindAddress"
|
|
|
|
|
actionArgBindAddressSnake = "bind_address"
|
|
|
|
|
actionArgIP = "ip"
|
|
|
|
|
actionArgAddress = "address"
|
|
|
|
|
actionArgName = "name"
|
|
|
|
|
actionArgHost = "host"
|
|
|
|
|
actionArgHostName = "hostname"
|
|
|
|
|
actionArgPort = "port"
|
|
|
|
|
actionArgDNSPort = "dnsPort"
|
|
|
|
|
actionArgDNSPortSnake = "dns_port"
|
|
|
|
|
actionArgHealthPort = "health_port"
|
|
|
|
|
actionArgHealthPortCamel = "healthPort"
|
2026-04-03 23:00:07 +00:00
|
|
|
)
|
|
|
|
|
|
2026-04-03 20:35:14 +00:00
|
|
|
type ActionDefinition struct {
|
2026-04-03 22:43:27 +00:00
|
|
|
Name string
|
|
|
|
|
Invoke func(map[string]any) (any, bool, error)
|
|
|
|
|
InvokeContext func(context.Context, map[string]any) (any, bool, error)
|
2026-04-03 20:35:14 +00:00
|
|
|
}
|
|
|
|
|
|
2026-04-03 21:11:25 +00:00
|
|
|
// ActionRegistrar publishes DNS actions into another Core surface.
|
2026-04-03 20:40:02 +00:00
|
|
|
//
|
2026-04-03 22:52:14 +00:00
|
|
|
// registrar.RegisterAction(ActionResolve, func(values map[string]any) (any, bool, error) {
|
2026-04-04 02:16:14 +00:00
|
|
|
// return service.ResolveAddresses("gateway.charon.lthn")
|
2026-04-03 21:09:20 +00:00
|
|
|
// })
|
2026-04-03 20:40:02 +00:00
|
|
|
type ActionRegistrar interface {
|
|
|
|
|
RegisterAction(name string, invoke func(map[string]any) (any, bool, error))
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 22:54:47 +00:00
|
|
|
// ActionContextRegistrar publishes DNS actions while preserving caller context.
|
|
|
|
|
//
|
|
|
|
|
// registrar.RegisterActionContext(
|
|
|
|
|
// ActionDiscover,
|
|
|
|
|
// func(ctx context.Context, values map[string]any) (any, bool, error) {
|
|
|
|
|
// return service.HandleActionContext(ctx, ActionDiscover, values)
|
|
|
|
|
// },
|
|
|
|
|
// )
|
|
|
|
|
type ActionContextRegistrar interface {
|
|
|
|
|
RegisterActionContext(name string, invoke func(context.Context, map[string]any) (any, bool, error))
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 21:23:17 +00:00
|
|
|
// ActionCaller resolves named actions from another Core surface.
|
|
|
|
|
//
|
2026-04-03 22:52:14 +00:00
|
|
|
// aliases, ok, err := caller.CallAction(
|
|
|
|
|
// context.Background(),
|
|
|
|
|
// "blockchain.chain.aliases",
|
|
|
|
|
// map[string]any{},
|
|
|
|
|
// )
|
2026-04-03 21:23:17 +00:00
|
|
|
type ActionCaller interface {
|
|
|
|
|
CallAction(ctx context.Context, name string, values map[string]any) (any, bool, error)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 20:35:14 +00:00
|
|
|
// ActionDefinitions returns the complete DNS action surface in registration order.
|
|
|
|
|
//
|
2026-04-03 20:48:18 +00:00
|
|
|
// definitions := service.ActionDefinitions()
|
2026-04-03 21:09:20 +00:00
|
|
|
// for _, definition := range definitions {
|
2026-04-03 21:46:03 +00:00
|
|
|
// fmt.Println(definition.Name)
|
2026-04-03 21:09:20 +00:00
|
|
|
// }
|
2026-04-04 02:40:44 +00:00
|
|
|
// // dns.resolve, dns.resolve.txt, dns.resolve.all, dns.reverse, dns.serve, dns.health, dns.discover
|
2026-04-03 20:35:14 +00:00
|
|
|
func (service *Service) ActionDefinitions() []ActionDefinition {
|
|
|
|
|
return []ActionDefinition{
|
|
|
|
|
{
|
|
|
|
|
Name: ActionResolve,
|
|
|
|
|
Invoke: func(values map[string]any) (any, bool, error) {
|
2026-04-03 22:43:27 +00:00
|
|
|
return service.handleResolveAddress(context.Background(), values)
|
|
|
|
|
},
|
|
|
|
|
InvokeContext: func(ctx context.Context, values map[string]any) (any, bool, error) {
|
|
|
|
|
return service.handleResolveAddress(ctx, values)
|
2026-04-03 20:35:14 +00:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Name: ActionResolveTXT,
|
|
|
|
|
Invoke: func(values map[string]any) (any, bool, error) {
|
2026-04-03 22:43:27 +00:00
|
|
|
return service.handleResolveTXTRecords(context.Background(), values)
|
|
|
|
|
},
|
|
|
|
|
InvokeContext: func(ctx context.Context, values map[string]any) (any, bool, error) {
|
|
|
|
|
return service.handleResolveTXTRecords(ctx, values)
|
2026-04-03 20:35:14 +00:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Name: ActionResolveAll,
|
|
|
|
|
Invoke: func(values map[string]any) (any, bool, error) {
|
2026-04-03 22:43:27 +00:00
|
|
|
return service.handleResolveAll(context.Background(), values)
|
|
|
|
|
},
|
|
|
|
|
InvokeContext: func(ctx context.Context, values map[string]any) (any, bool, error) {
|
|
|
|
|
return service.handleResolveAll(ctx, values)
|
2026-04-03 20:35:14 +00:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Name: ActionReverse,
|
|
|
|
|
Invoke: func(values map[string]any) (any, bool, error) {
|
2026-04-03 22:43:27 +00:00
|
|
|
return service.handleReverseLookup(context.Background(), values)
|
|
|
|
|
},
|
|
|
|
|
InvokeContext: func(ctx context.Context, values map[string]any) (any, bool, error) {
|
|
|
|
|
return service.handleReverseLookup(ctx, values)
|
2026-04-03 20:35:14 +00:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Name: ActionServe,
|
|
|
|
|
Invoke: func(values map[string]any) (any, bool, error) {
|
2026-04-03 22:43:27 +00:00
|
|
|
return service.handleServe(context.Background(), values)
|
|
|
|
|
},
|
|
|
|
|
InvokeContext: func(ctx context.Context, values map[string]any) (any, bool, error) {
|
|
|
|
|
return service.handleServe(ctx, values)
|
2026-04-03 20:35:14 +00:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Name: ActionHealth,
|
|
|
|
|
Invoke: func(map[string]any) (any, bool, error) {
|
|
|
|
|
return service.Health(), true, nil
|
|
|
|
|
},
|
2026-04-03 22:43:27 +00:00
|
|
|
InvokeContext: func(_ context.Context, _ map[string]any) (any, bool, error) {
|
|
|
|
|
return service.Health(), true, nil
|
|
|
|
|
},
|
2026-04-03 20:35:14 +00:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Name: ActionDiscover,
|
|
|
|
|
Invoke: func(map[string]any) (any, bool, error) {
|
|
|
|
|
if err := service.DiscoverAliases(context.Background()); err != nil {
|
|
|
|
|
return nil, false, err
|
|
|
|
|
}
|
2026-04-04 02:52:17 +00:00
|
|
|
return nil, true, nil
|
2026-04-03 20:35:14 +00:00
|
|
|
},
|
2026-04-03 22:43:27 +00:00
|
|
|
InvokeContext: func(ctx context.Context, _ map[string]any) (any, bool, error) {
|
|
|
|
|
if err := service.DiscoverAliases(ctx); err != nil {
|
|
|
|
|
return nil, false, err
|
|
|
|
|
}
|
2026-04-04 02:52:17 +00:00
|
|
|
return nil, true, nil
|
2026-04-03 22:43:27 +00:00
|
|
|
},
|
2026-04-03 20:35:14 +00:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ActionNames returns the names of the registered DNS actions.
|
|
|
|
|
//
|
2026-04-03 20:48:18 +00:00
|
|
|
// names := service.ActionNames()
|
2026-04-03 21:20:09 +00:00
|
|
|
// // []string{"dns.resolve", "dns.resolve.txt", "dns.resolve.all", "dns.reverse", "dns.serve", "dns.health", "dns.discover"}
|
2026-04-03 20:35:14 +00:00
|
|
|
func (service *Service) ActionNames() []string {
|
|
|
|
|
definitions := service.ActionDefinitions()
|
|
|
|
|
names := make([]string, 0, len(definitions))
|
|
|
|
|
for _, definition := range definitions {
|
|
|
|
|
names = append(names, definition.Name)
|
|
|
|
|
}
|
|
|
|
|
return names
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 20:40:02 +00:00
|
|
|
// RegisterActions publishes the DNS action surface to a registrar in definition order.
|
|
|
|
|
//
|
|
|
|
|
// service.RegisterActions(registrar)
|
2026-04-03 21:46:03 +00:00
|
|
|
// // registrar.RegisterAction("dns.health", ...)
|
2026-04-04 02:40:44 +00:00
|
|
|
// // registrar.RegisterActionContext("dns.discover", func(ctx context.Context, values map[string]any) (any, bool, error) { ... })
|
2026-04-03 20:40:02 +00:00
|
|
|
func (service *Service) RegisterActions(registrar ActionRegistrar) {
|
|
|
|
|
if registrar == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-04-03 22:54:47 +00:00
|
|
|
|
|
|
|
|
if contextRegistrar, ok := registrar.(ActionContextRegistrar); ok {
|
|
|
|
|
for _, definition := range service.ActionDefinitions() {
|
|
|
|
|
invoke := definition.InvokeContext
|
|
|
|
|
if invoke == nil {
|
|
|
|
|
invoke = func(ctx context.Context, values map[string]any) (any, bool, error) {
|
|
|
|
|
return definition.Invoke(values)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
contextRegistrar.RegisterActionContext(definition.Name, invoke)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 20:40:02 +00:00
|
|
|
for _, definition := range service.ActionDefinitions() {
|
|
|
|
|
registrar.RegisterAction(definition.Name, definition.Invoke)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 21:30:08 +00:00
|
|
|
// NewServiceWithRegistrar builds a DNS service and registers its actions in one step.
|
|
|
|
|
//
|
2026-04-03 23:45:05 +00:00
|
|
|
// Deprecated: use NewDNSServiceWithRegistrar for a more explicit constructor name.
|
|
|
|
|
//
|
2026-04-03 22:52:14 +00:00
|
|
|
// service := dns.NewServiceWithRegistrar(dns.ServiceConfiguration{}, registrar)
|
2026-04-03 21:30:08 +00:00
|
|
|
// // registrar now exposes dns.resolve, dns.resolve.txt, dns.resolve.all, dns.reverse, dns.serve, dns.health, dns.discover
|
|
|
|
|
func NewServiceWithRegistrar(options ServiceOptions, registrar ActionRegistrar) *Service {
|
2026-04-03 22:11:00 +00:00
|
|
|
if registrar != nil {
|
|
|
|
|
options.ActionRegistrar = registrar
|
|
|
|
|
}
|
|
|
|
|
return NewService(options)
|
2026-04-03 21:30:08 +00:00
|
|
|
}
|
|
|
|
|
|
2026-04-03 23:45:05 +00:00
|
|
|
// NewDNSServiceWithRegistrar builds a DNS service and registers its actions in one step.
|
|
|
|
|
//
|
|
|
|
|
// service := dns.NewDNSServiceWithRegistrar(dns.ServiceConfiguration{}, registrar)
|
|
|
|
|
// // registrar now exposes dns.resolve, dns.resolve.txt, dns.resolve.all, dns.reverse, dns.serve, dns.health, dns.discover
|
|
|
|
|
func NewDNSServiceWithRegistrar(options ServiceOptions, registrar ActionRegistrar) *Service {
|
|
|
|
|
return NewServiceWithRegistrar(options, registrar)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 20:15:36 +00:00
|
|
|
// HandleAction executes a DNS action by name.
|
|
|
|
|
//
|
2026-04-03 21:09:20 +00:00
|
|
|
// payload, ok, err := service.HandleAction(ActionResolve, map[string]any{
|
|
|
|
|
// "name": "gateway.charon.lthn",
|
|
|
|
|
// })
|
2026-04-03 20:15:36 +00:00
|
|
|
func (service *Service) HandleAction(name string, values map[string]any) (any, bool, error) {
|
2026-04-03 22:43:27 +00:00
|
|
|
return service.HandleActionContext(context.Background(), name, values)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HandleActionContext executes a DNS action with the supplied context.
|
|
|
|
|
//
|
|
|
|
|
// payload, ok, err := service.HandleActionContext(ctx, ActionResolve, map[string]any{
|
|
|
|
|
// "name": "gateway.charon.lthn",
|
|
|
|
|
// })
|
|
|
|
|
func (service *Service) HandleActionContext(ctx context.Context, name string, values map[string]any) (any, bool, error) {
|
|
|
|
|
if ctx == nil {
|
|
|
|
|
ctx = context.Background()
|
|
|
|
|
}
|
2026-04-03 20:35:14 +00:00
|
|
|
for _, definition := range service.ActionDefinitions() {
|
|
|
|
|
if definition.Name == name {
|
2026-04-03 22:43:27 +00:00
|
|
|
if definition.InvokeContext != nil {
|
|
|
|
|
return definition.InvokeContext(ctx, values)
|
|
|
|
|
}
|
2026-04-03 20:35:14 +00:00
|
|
|
return definition.Invoke(values)
|
2026-04-03 20:15:36 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-04-03 20:35:14 +00:00
|
|
|
return nil, false, errActionNotFound
|
2026-04-03 20:15:36 +00:00
|
|
|
}
|
|
|
|
|
|
2026-04-03 22:43:27 +00:00
|
|
|
func (service *Service) handleResolveAddress(ctx context.Context, values map[string]any) (any, bool, error) {
|
|
|
|
|
_ = ctx
|
2026-04-04 00:06:46 +00:00
|
|
|
host, err := stringActionValueFromKeys(values, actionArgName, actionArgHost, actionArgHostName)
|
2026-04-03 22:43:27 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, false, err
|
|
|
|
|
}
|
2026-04-04 02:16:14 +00:00
|
|
|
result, ok := service.ResolveAddresses(host)
|
2026-04-03 22:43:27 +00:00
|
|
|
if !ok {
|
|
|
|
|
return nil, false, nil
|
|
|
|
|
}
|
|
|
|
|
return result, true, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (service *Service) handleResolveTXTRecords(ctx context.Context, values map[string]any) (any, bool, error) {
|
|
|
|
|
_ = ctx
|
2026-04-04 00:06:46 +00:00
|
|
|
host, err := stringActionValueFromKeys(values, actionArgName, actionArgHost, actionArgHostName)
|
2026-04-03 22:43:27 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, false, err
|
|
|
|
|
}
|
|
|
|
|
result, ok := service.ResolveTXTRecords(host)
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil, false, nil
|
|
|
|
|
}
|
|
|
|
|
return result, true, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (service *Service) handleResolveAll(ctx context.Context, values map[string]any) (any, bool, error) {
|
|
|
|
|
_ = ctx
|
2026-04-04 00:06:46 +00:00
|
|
|
host, err := stringActionValueFromKeys(values, actionArgName, actionArgHost, actionArgHostName)
|
2026-04-03 22:43:27 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, false, err
|
|
|
|
|
}
|
|
|
|
|
result, ok := service.ResolveAll(host)
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil, false, nil
|
|
|
|
|
}
|
|
|
|
|
return result, true, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (service *Service) handleReverseLookup(ctx context.Context, values map[string]any) (any, bool, error) {
|
|
|
|
|
_ = ctx
|
2026-04-04 03:26:45 +00:00
|
|
|
ip, err := stringActionValueFromKeys(values, actionArgIP, actionArgAddress, actionArgName, actionArgHost, actionArgHostName)
|
2026-04-03 22:43:27 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, false, err
|
|
|
|
|
}
|
|
|
|
|
result, ok := service.ResolveReverseNames(ip)
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil, false, nil
|
|
|
|
|
}
|
|
|
|
|
return result, true, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (service *Service) handleServe(ctx context.Context, values map[string]any) (any, bool, error) {
|
|
|
|
|
_ = ctx
|
2026-04-04 00:20:49 +00:00
|
|
|
bind, _, err := stringActionValueOptionalFromKeys(values, actionArgBind, actionArgBindAddress, actionArgBindAddressSnake)
|
2026-04-03 23:19:06 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, false, err
|
|
|
|
|
}
|
2026-04-04 00:13:41 +00:00
|
|
|
port, portProvided, err := intActionValueOptionalFromKeys(values, actionArgPort, actionArgDNSPort, actionArgDNSPortSnake)
|
2026-04-03 22:43:27 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, false, err
|
|
|
|
|
}
|
2026-04-03 22:58:35 +00:00
|
|
|
if !portProvided {
|
2026-04-03 23:24:28 +00:00
|
|
|
port = service.ResolveDNSPort()
|
2026-04-03 22:58:35 +00:00
|
|
|
}
|
2026-04-03 23:38:00 +00:00
|
|
|
|
2026-04-04 00:00:51 +00:00
|
|
|
healthPort, healthPortProvided, err := intActionValueOptionalFromKeys(values, actionArgHealthPort, actionArgHealthPortCamel)
|
2026-04-03 23:38:00 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, false, err
|
|
|
|
|
}
|
2026-04-03 23:58:59 +00:00
|
|
|
if !healthPortProvided && service.httpPort > 0 {
|
|
|
|
|
runtime, err := service.ServeAll(bind, port, service.httpPort)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, false, err
|
|
|
|
|
}
|
|
|
|
|
return runtime, true, nil
|
|
|
|
|
}
|
2026-04-03 23:38:00 +00:00
|
|
|
if healthPortProvided {
|
|
|
|
|
runtime, err := service.ServeAll(bind, port, healthPort)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, false, err
|
|
|
|
|
}
|
|
|
|
|
return runtime, true, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 22:43:27 +00:00
|
|
|
result, err := service.Serve(bind, port)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, false, err
|
|
|
|
|
}
|
|
|
|
|
return result, true, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 20:15:36 +00:00
|
|
|
func stringActionValue(values map[string]any, key string) (string, error) {
|
|
|
|
|
if values == nil {
|
|
|
|
|
return "", errActionMissingValue
|
|
|
|
|
}
|
|
|
|
|
raw, exists := values[key]
|
|
|
|
|
if !exists {
|
|
|
|
|
return "", errActionMissingValue
|
|
|
|
|
}
|
|
|
|
|
if value, ok := raw.(string); ok {
|
2026-04-03 23:00:07 +00:00
|
|
|
value = strings.TrimSpace(value)
|
|
|
|
|
if value == "" {
|
|
|
|
|
return "", errActionMissingValue
|
|
|
|
|
}
|
2026-04-03 20:15:36 +00:00
|
|
|
return value, nil
|
|
|
|
|
}
|
|
|
|
|
return "", errActionMissingValue
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 00:19:10 +00:00
|
|
|
func lookupActionValue(values map[string]any, keys ...string) (any, string, bool) {
|
|
|
|
|
if values == nil {
|
|
|
|
|
return nil, "", false
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 00:06:46 +00:00
|
|
|
for _, key := range keys {
|
2026-04-04 00:19:10 +00:00
|
|
|
if value, exists := values[key]; exists {
|
|
|
|
|
return value, key, true
|
2026-04-04 00:06:46 +00:00
|
|
|
}
|
2026-04-04 00:19:10 +00:00
|
|
|
}
|
2026-04-04 00:06:46 +00:00
|
|
|
|
2026-04-04 00:19:10 +00:00
|
|
|
for _, key := range keys {
|
|
|
|
|
for alias, value := range values {
|
|
|
|
|
if strings.EqualFold(alias, key) {
|
|
|
|
|
return value, alias, true
|
|
|
|
|
}
|
2026-04-04 00:06:46 +00:00
|
|
|
}
|
2026-04-04 00:19:10 +00:00
|
|
|
}
|
2026-04-04 00:06:46 +00:00
|
|
|
|
2026-04-04 00:19:10 +00:00
|
|
|
return nil, "", false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func stringActionValueFromKeys(values map[string]any, keys ...string) (string, error) {
|
|
|
|
|
value, key, found := lookupActionValue(values, keys...)
|
|
|
|
|
if !found {
|
|
|
|
|
return "", errActionMissingValue
|
|
|
|
|
}
|
|
|
|
|
text, ok := value.(string)
|
|
|
|
|
if !ok {
|
|
|
|
|
return "", fmt.Errorf("%w: %s", errActionMissingValue, key)
|
2026-04-04 00:06:46 +00:00
|
|
|
}
|
|
|
|
|
|
2026-04-04 00:19:10 +00:00
|
|
|
text = strings.TrimSpace(text)
|
|
|
|
|
if text == "" {
|
|
|
|
|
return "", errActionMissingValue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return text, nil
|
2026-04-04 00:06:46 +00:00
|
|
|
}
|
|
|
|
|
|
2026-04-03 20:15:36 +00:00
|
|
|
func stringActionValueOptional(values map[string]any, key string) (string, error) {
|
|
|
|
|
if values == nil {
|
|
|
|
|
return "", nil
|
|
|
|
|
}
|
|
|
|
|
raw, exists := values[key]
|
|
|
|
|
if !exists {
|
|
|
|
|
return "", nil
|
|
|
|
|
}
|
|
|
|
|
value, ok := raw.(string)
|
|
|
|
|
if !ok {
|
|
|
|
|
return "", fmt.Errorf("%w: %s", errActionMissingValue, key)
|
|
|
|
|
}
|
2026-04-03 23:00:07 +00:00
|
|
|
return strings.TrimSpace(value), nil
|
2026-04-03 20:15:36 +00:00
|
|
|
}
|
|
|
|
|
|
2026-04-04 00:00:51 +00:00
|
|
|
func stringActionValueOptionalFromKeys(values map[string]any, keys ...string) (string, bool, error) {
|
2026-04-04 00:19:10 +00:00
|
|
|
value, key, found := lookupActionValue(values, keys...)
|
|
|
|
|
if !found {
|
|
|
|
|
return "", false, nil
|
|
|
|
|
}
|
|
|
|
|
text, ok := value.(string)
|
|
|
|
|
if !ok {
|
|
|
|
|
return "", false, fmt.Errorf("%w: %s", errActionMissingValue, key)
|
2026-04-04 00:00:51 +00:00
|
|
|
}
|
2026-04-04 00:19:10 +00:00
|
|
|
return strings.TrimSpace(text), true, nil
|
2026-04-04 00:00:51 +00:00
|
|
|
}
|
|
|
|
|
|
2026-04-03 20:15:36 +00:00
|
|
|
func intActionValue(values map[string]any, key string) (int, error) {
|
|
|
|
|
if values == nil {
|
|
|
|
|
return 0, errActionMissingValue
|
|
|
|
|
}
|
|
|
|
|
raw, exists := values[key]
|
|
|
|
|
if !exists {
|
|
|
|
|
return 0, errActionMissingValue
|
|
|
|
|
}
|
|
|
|
|
switch value := raw.(type) {
|
|
|
|
|
case int:
|
|
|
|
|
return value, nil
|
2026-04-03 23:00:07 +00:00
|
|
|
case uint:
|
|
|
|
|
return int(value), nil
|
|
|
|
|
case uint8:
|
|
|
|
|
return int(value), nil
|
|
|
|
|
case uint16:
|
|
|
|
|
return int(value), nil
|
|
|
|
|
case uint32:
|
|
|
|
|
return int(value), nil
|
|
|
|
|
case uint64:
|
|
|
|
|
if value > uint64(^uint(0)>>1) {
|
|
|
|
|
return 0, fmt.Errorf("%w: %s", errActionMissingValue, key)
|
|
|
|
|
}
|
|
|
|
|
return int(value), nil
|
2026-04-03 20:15:36 +00:00
|
|
|
case int32:
|
|
|
|
|
return int(value), nil
|
|
|
|
|
case int64:
|
2026-04-03 23:00:07 +00:00
|
|
|
if value > int64(int(^uint(0)>>1)) || value < int64(^uint(0)>>1)*-1-1 {
|
|
|
|
|
return 0, fmt.Errorf("%w: %s", errActionMissingValue, key)
|
|
|
|
|
}
|
2026-04-03 20:15:36 +00:00
|
|
|
return int(value), nil
|
|
|
|
|
case float64:
|
2026-04-03 23:00:07 +00:00
|
|
|
if math.Trunc(value) != value {
|
|
|
|
|
return 0, fmt.Errorf("%w: %s", errActionMissingValue, key)
|
|
|
|
|
}
|
|
|
|
|
if value < 0 || value > float64(int(^uint(0)>>1)) {
|
|
|
|
|
return 0, fmt.Errorf("%w: %s", errActionMissingValue, key)
|
|
|
|
|
}
|
2026-04-03 20:15:36 +00:00
|
|
|
return int(value), nil
|
|
|
|
|
case float32:
|
2026-04-03 23:00:07 +00:00
|
|
|
floating := float64(value)
|
|
|
|
|
if math.Trunc(floating) != floating {
|
|
|
|
|
return 0, fmt.Errorf("%w: %s", errActionMissingValue, key)
|
|
|
|
|
}
|
|
|
|
|
if floating < 0 || floating > float64(int(^uint(0)>>1)) {
|
|
|
|
|
return 0, fmt.Errorf("%w: %s", errActionMissingValue, key)
|
|
|
|
|
}
|
|
|
|
|
return int(floating), nil
|
|
|
|
|
case json.Number:
|
|
|
|
|
parsed, err := value.Int64()
|
|
|
|
|
if err == nil {
|
|
|
|
|
if parsed > int64(int(^uint(0)>>1)) || parsed < int64(^uint(0)>>1)*-1-1 {
|
|
|
|
|
return 0, fmt.Errorf("%w: %s", errActionMissingValue, key)
|
|
|
|
|
}
|
|
|
|
|
return int(parsed), nil
|
|
|
|
|
}
|
|
|
|
|
floating, parseErr := value.Float64()
|
|
|
|
|
if parseErr != nil {
|
|
|
|
|
return 0, fmt.Errorf("%w: %s", errActionMissingValue, key)
|
|
|
|
|
}
|
|
|
|
|
if math.Trunc(floating) != floating {
|
|
|
|
|
return 0, fmt.Errorf("%w: %s", errActionMissingValue, key)
|
|
|
|
|
}
|
|
|
|
|
if floating < 0 || floating > float64(int(^uint(0)>>1)) {
|
|
|
|
|
return 0, fmt.Errorf("%w: %s", errActionMissingValue, key)
|
|
|
|
|
}
|
|
|
|
|
return int(floating), nil
|
2026-04-03 20:15:36 +00:00
|
|
|
default:
|
|
|
|
|
return 0, fmt.Errorf("%w: %s", errActionMissingValue, key)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-03 22:58:35 +00:00
|
|
|
|
|
|
|
|
func intActionValueOptional(values map[string]any, key string) (int, bool, error) {
|
|
|
|
|
if values == nil {
|
|
|
|
|
return 0, false, nil
|
|
|
|
|
}
|
|
|
|
|
_, exists := values[key]
|
|
|
|
|
if !exists {
|
|
|
|
|
return 0, false, nil
|
|
|
|
|
}
|
|
|
|
|
value, err := intActionValue(values, key)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, false, err
|
|
|
|
|
}
|
|
|
|
|
return value, true, nil
|
|
|
|
|
}
|
2026-04-04 00:00:51 +00:00
|
|
|
|
|
|
|
|
func intActionValueOptionalFromKeys(values map[string]any, keys ...string) (int, bool, error) {
|
|
|
|
|
for _, key := range keys {
|
|
|
|
|
value, valueProvided, err := intActionValueOptional(values, key)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, false, err
|
|
|
|
|
}
|
|
|
|
|
if valueProvided {
|
|
|
|
|
return value, true, nil
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-04 00:19:10 +00:00
|
|
|
|
|
|
|
|
value, key, found := lookupActionValue(values, keys...)
|
|
|
|
|
if !found {
|
|
|
|
|
return 0, false, nil
|
|
|
|
|
}
|
|
|
|
|
intValue, err := intActionValue(map[string]any{key: value}, key)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, false, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return intValue, true, nil
|
2026-04-04 00:00:51 +00:00
|
|
|
}
|