function

package module
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 16, 2025 License: MIT Imports: 14 Imported by: 4

README

go-function

Go Reference Go Report Card

Wrapping Go functions as HTTP handlers, CLI commands, and more

go-function is a Go library that provides utilities for wrapping any Go function to enable multiple calling conventions: direct calls with typed arguments, string-based calls, JSON calls, HTTP handlers, and CLI commands.

Features

  • Multiple Calling Conventions: Call functions with []any, strings, named strings, or JSON
  • Automatic Type Conversion: Convert strings to the correct types automatically
  • HTTP Integration: Turn functions into HTTP handlers with httpfun package
  • CLI Integration: Build command-line interfaces with cli package
  • HTML Forms: Generate HTML forms from function signatures with htmlform package
  • Code Generation: Generate zero-overhead wrappers with gen-func-wrappers
  • Reflection Fallback: Use reflection-based wrappers when code generation isn't needed
  • Context Support: Automatic context.Context handling
  • Error Handling: Proper error propagation and panic recovery

Installation

go get github.com/domonda/go-function

Quick Start

Basic Function Wrapping
package main

import (
    "context"
    "fmt"
    "github.com/domonda/go-function"
)

func Add(a, b int) int {
    return a + b
}

func Greet(ctx context.Context, name string) (string, error) {
    return fmt.Sprintf("Hello, %s!", name), nil
}

func main() {
    // Wrap functions using reflection
    addWrapper := function.MustReflectWrapper("Add", Add)
    greetWrapper := function.MustReflectWrapper("Greet", Greet)

    ctx := context.Background()

    // Call with string arguments
    results, _ := addWrapper.CallWithStrings(ctx, "5", "3")
    fmt.Println(results[0]) // Output: 8

    // Call with named arguments
    results, _ := greetWrapper.CallWithNamedStrings(ctx, map[string]string{
        "name": "World",
    })
    fmt.Println(results[0]) // Output: Hello, World!

    // Call with JSON
    results, _ := addWrapper.CallWithJSON(ctx, []byte(`[10, 20]`))
    fmt.Println(results[0]) // Output: 30
}
HTTP Handler
package main

import (
    "context"
    "net/http"
    "github.com/domonda/go-function/httpfun"
)

func Calculate(ctx context.Context, operation string, a, b int) (int, error) {
    switch operation {
    case "add":
        return a + b, nil
    case "multiply":
        return a * b, nil
    default:
        return 0, fmt.Errorf("unknown operation: %s", operation)
    }
}

func main() {
    // Create HTTP handler from function
    handler := httpfun.NewHandler(
        function.MustReflectWrapper("Calculate", Calculate),
        nil, // Use default result writer
    )

    http.Handle("/calculate", handler)
    http.ListenAndServe(":8080", nil)

    // Usage:
    // GET /calculate?operation=add&a=5&b=3 -> 8
    // POST /calculate with JSON: {"operation":"multiply","a":5,"b":3} -> 15
}
CLI Command
package main

import (
    "context"
    "fmt"
    "os"
    "github.com/domonda/go-function"
    "github.com/domonda/go-function/cli"
)

func Deploy(env, service string, version int) error {
    fmt.Printf("Deploying %s v%d to %s\n", service, version, env)
    return nil
}

func main() {
    dispatcher := cli.NewStringArgsDispatcher(context.Background())
    dispatcher.MustAddCommand("", function.MustReflectWrapper("deploy", Deploy))

    err := dispatcher.Dispatch(os.Args[1:]...)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Error: %v\n", err)
        os.Exit(1)
    }
}

// Usage:
// $ myapp deploy production api-server 42
// Deploying api-server v42 to production

Code Generation for Better Performance

For production use, generate optimized wrappers without reflection overhead:

// In your code, use placeholder functions
package myapp

import "github.com/domonda/go-function"

func Calculate(a, b int) int {
    return a + b
}

// Use WrapperTODO during development
var CalculateWrapper = function.WrapperTODO(Calculate)

Then run the code generator:

go run github.com/domonda/go-function/cmd/gen-func-wrappers

This replaces WrapperTODO calls with generated, type-safe wrapper code that has zero reflection overhead.

Supported Function Signatures

The library supports various function signatures:

// No arguments or results
func SimpleFunc()

// With context
func WithContext(ctx context.Context, arg string) error

// Multiple arguments and results
func Complex(ctx context.Context, a int, b string) (result int, err error)

// Variadic functions (limited support)
func Variadic(args ...string) string

Requirements:

  • If present, context.Context must be the first argument
  • If present, error must be the last result
  • All arguments and results must be supported types (or implement encoding.TextUnmarshaler / json.Unmarshaler)

Packages

Core Package (function)
  • Wrapper - Main interface for wrapped functions
  • Description - Function metadata (name, arguments, types)
  • StringScanner - String-to-type conversion
  • ReflectWrapper - Reflection-based wrapper
  • WrapperTODO - Placeholder for code generation
HTTP Package (httpfun)
  • NewHandler - Create HTTP handler from function
  • HandlerWithoutContext - HTTP handler without context arg
  • RequestArgs - Parse function arguments from HTTP requests
  • ResultsWriter - Write function results as HTTP responses
CLI Package (cli)
  • StringArgsDispatcher - Command dispatcher for CLI apps
  • SuperStringArgsDispatcher - Multi-level command dispatcher
  • Complete - Shell completion support
HTML Forms Package (htmlform)
  • NewHandler - Generate HTML forms for functions
  • Form generation with validation
  • Customizable templates
Code Generator (cmd/gen-func-wrappers)
  • Generates optimized wrapper code
  • Zero reflection overhead
  • Preserves type safety
  • Supports custom import prefixes

Configuration

String Scanners

The library automatically converts strings to the required types:

// Basic types
var i int
function.ScanString("42", &i) // i = 42

var f float64
function.ScanString("3.14", &f) // f = 3.14

var b bool
function.ScanString("true", &b) // b = true

// Time types
var t time.Time
function.ScanString("2024-01-15", &t)

var d time.Duration
function.ScanString("5m30s", &d) // d = 5*time.Minute + 30*time.Second

// Slices
var nums []int
function.ScanString("[1,2,3]", &nums) // nums = []int{1,2,3}

// Structs (as JSON)
type Person struct {
    Name string
    Age  int
}
var p Person
function.ScanString(`{"Name":"Alice","Age":30}`, &p)

// Nil values
var ptr *int
function.ScanString("nil", &ptr) // ptr = nil

But you can customize type to string conversions:

import "github.com/domonda/go-function"

// Add custom scanner for a specific type
function.StringScanners.SetForType(
    reflect.TypeOf(MyCustomType{}),
    function.StringScannerFunc(func(src string, dest any) error {
        // Custom conversion logic
        return nil
    }),
)

// Configure time formats
function.TimeFormats = []string{
    time.RFC3339,
    "2006-01-02",
    "2006-01-02 15:04",
}
HTTP Configuration
import "github.com/domonda/go-function/httpfun"

// Pretty-print JSON responses
httpfun.PrettyPrint = true
httpfun.PrettyPrintIndent = "  "

// Custom error handler
httpfun.HandleError = func(err error, w http.ResponseWriter, r *http.Request) {
    // Custom error handling
}

// Panic recovery
httpfun.CatchHandlerPanics = true

Advanced Examples

Custom Result Handler
// Custom handler that always returns uppercase strings
customHandler := httpfun.ResultsWriterFunc(func(results []any, w http.ResponseWriter, r *http.Request) error {
    if len(results) > 0 {
        if str, ok := results[0].(string); ok {
            w.Write([]byte(strings.ToUpper(str)))
            return nil
        }
    }
    return httpfun.DefaultResultsWriter.WriteResults(results, w, r)
})

handler := httpfun.NewHandler(wrapper, customHandler)
Multi-level CLI Commands
// Build CLI with subcommands: app user create, app user delete, etc.
dispatcher := cli.NewSuperStringArgsDispatcher(context.Background())

userDispatcher := dispatcher.AddSubDispatcher("user")
userDispatcher.MustAddCommand("create", function.MustReflectWrapper("CreateUser", CreateUser))
userDispatcher.MustAddCommand("delete", function.MustReflectWrapper("DeleteUser", DeleteUser))

dbDispatcher := dispatcher.AddSubDispatcher("db")
dbDispatcher.MustAddCommand("migrate", function.MustReflectWrapper("Migrate", Migrate))
dbDispatcher.MustAddCommand("seed", function.MustReflectWrapper("Seed", Seed))

dispatcher.Dispatch(os.Args[1:]...)

Testing

The library includes comprehensive tests. Run them with:

go test ./...

Contributing

Contributions are welcome! Please:

  1. Add tests for new features
  2. Update documentation
  3. Follow existing code style
  4. Ensure go test ./... passes

License

MIT License - see LICENSE file for details

  • go-errs - Error wrapping used by this library
  • go-types - Type definitions with string scanning support
  • go-astvisit - AST manipulation utilities used by the code generator

Documentation

Overview

Package function provides utilities for wrapping Go functions to enable multiple calling conventions including strings, JSON, HTTP handlers, and CLI commands.

Overview

This package allows you to wrap any Go function and call it through different interfaces:

  • Direct calls with []any arguments
  • Calls with string arguments that are automatically converted to the correct types
  • Calls with named string arguments (map[string]string)
  • Calls with JSON-encoded arguments
  • HTTP handlers that invoke the function
  • CLI commands that parse command-line arguments

Basic Usage

There are two main approaches to wrapping functions:

1. Using reflection (runtime overhead):

func Add(a, b int) int {
    return a + b
}

wrapper := function.MustReflectWrapper("Add", Add)
results, err := wrapper.CallWithStrings(ctx, "5", "3")
// results[0] == 8

2. Using code generation (no runtime overhead):

// In your code, use placeholder functions:
var AddWrapper = function.WrapperTODO(Add)

// Then run gen-func-wrappers to replace with generated code:
// go run github.com/domonda/go-function/cmd/gen-func-wrappers

Function Signatures

The package supports various function signatures:

  • With or without context.Context as first argument
  • With or without error as last result
  • Zero or more arguments of any type
  • Zero or more results of any type

Examples:

func SimpleFunc(a int) string
func WithContext(ctx context.Context, a int) (string, error)
func NoArgs() error
func NoResults(a string)
func Complex(ctx context.Context, a int, b string, c bool) (int, string, error)

String Conversion

The package automatically converts strings to function argument types:

  • Basic types: int, float, bool, string
  • Time types: time.Time, time.Duration
  • Pointers: converts "nil"/"null" to nil
  • Slices and arrays: parses JSON-like syntax [1,2,3]
  • Structs: parses JSON
  • Custom types: supports encoding.TextUnmarshaler and json.Unmarshaler

String conversion can be customized via StringScanners configuration.

Sub-packages

The package includes several sub-packages for specific use cases:

  • httpfun: Create HTTP handlers from functions
  • htmlform: Generate HTML forms for function arguments
  • cli: Build command-line interfaces from functions
  • cmd/gen-func-wrappers: Code generator for zero-overhead wrappers

Performance Considerations

Reflection-based wrappers (ReflectWrapper) have runtime overhead due to reflection and type conversion. For performance-critical code, use the code generator (gen-func-wrappers) to create specialized wrappers with no reflection overhead.

Error Handling

Functions can return an error as the last result. The wrapper will return this error through the error return value of the Call* methods. If the function doesn't return an error, any panics during execution will be recovered and returned as errors (when CatchHandlerPanics is enabled).

Context Support

If the first argument is context.Context, the wrapper will automatically pass the context provided to Call* methods. This enables proper cancellation, timeouts, and context value propagation.

Index

Constants

This section is empty.

Variables

View Source
var (
	StringScanners = NewTypeStringScanners(StringScannerFunc(DefaultScanString))

	// TimeFormats used in that order to try parse time strings.
	// If a time format has not time zone part,
	// then the date is returned in the local time zone.
	TimeFormats = []string{
		time.RFC3339Nano,
		time.RFC3339,
		time.DateOnly + " 15:04:05.999999999 -0700 MST",
		time.DateTime,
		time.DateOnly + " 15:04",
		time.DateOnly + "T15:04",
		time.DateOnly,
	}
)
View Source
var (
	// ErrTypeNotSupported indicates that a type is not supported
	ErrTypeNotSupported = errors.New("type not supported")
)

Functions

func CallFunctionWithJSONArgs

func CallFunctionWithJSONArgs(ctx context.Context, f Wrapper, jsonObject []byte) (results []any, err error)

func DefaultScanString

func DefaultScanString(sourceStr string, destPtr any) (err error)

DefaultScanString is the default string-to-type conversion function. It validates that destPtr is a non-nil pointer and delegates to scanString for the actual conversion logic.

Special string values:

  • Empty string, "nil", or "null" are treated as zero/nil values
  • Whitespace is trimmed for most types (except string itself)

Returns an error if destPtr is nil, not a pointer, or conversion fails.

func ScanString

func ScanString(sourceStr string, destPtr any) error

ScanString converts a string to the type pointed to by destPtr. It uses the globally configured StringScanners to perform type conversion.

Supported conversions include:

  • Basic types: int, float, bool, string, byte slice
  • Time types: time.Time (multiple formats), time.Duration
  • Pointers: "nil" or "null" converts to nil
  • Slices/arrays: JSON-like syntax [1,2,3] or ["a","b"]
  • Structs: JSON object syntax
  • Custom types: encoding.TextUnmarshaler, json.Unmarshaler, or types with SetNull() method

Example:

var i int
err := ScanString("42", &i)  // i = 42

var t time.Time
err := ScanString("2024-01-15", &t)

var slice []int
err := ScanString("[1,2,3]", &slice)  // slice = []int{1,2,3}

func ScanStrings

func ScanStrings(sourceStrings []string, destPtrs ...any) error

ScanStrings converts multiple strings to their respective destination types. It scans sourceStrings[i] into destPtrs[i] for each index. If the slices have different lengths, only the minimum number of elements are scanned. Returns an error on the first conversion failure.

Example:

var a int
var b string
var c bool
err := ScanStrings([]string{"42", "hello", "true"}, &a, &b, &c)

Types

type CallWithJSONWrapper

type CallWithJSONWrapper interface {
	CallWithJSON(ctx context.Context, argsJSON []byte) (results []any, err error)
}

CallWithJSONWrapper provides the ability to call a function with JSON-encoded arguments. The JSON can be either an array of arguments or an object with named arguments. Arguments are unmarshaled using json.Unmarshal into the required types.

func CallWithJSONWrapperTODO

func CallWithJSONWrapperTODO(function any) CallWithJSONWrapper

CallWithJSONWrapperTODO is a placeholder for code generation. See WrapperTODO for details.

type CallWithJSONWrapperFunc

type CallWithJSONWrapperFunc func(ctx context.Context, argsJSON []byte) (results []any, err error)

CallWithJSONWrapperFunc is a function type that implements CallWithJSONWrapper. It allows any function with the matching signature to be used as a CallWithJSONWrapper.

func (CallWithJSONWrapperFunc) CallWithJSON

func (f CallWithJSONWrapperFunc) CallWithJSON(ctx context.Context, argsJSON []byte) (results []any, err error)

type CallWithNamedStringsWrapper

type CallWithNamedStringsWrapper interface {
	CallWithNamedStrings(ctx context.Context, args map[string]string) (results []any, err error)
}

CallWithNamedStringsWrapper provides the ability to call a function with named string arguments. The argument names must match the function's parameter names. String values are automatically converted to the required types. Missing arguments will use zero values.

func CallWithNamedStringsWrapperTODO

func CallWithNamedStringsWrapperTODO(function any) CallWithNamedStringsWrapper

CallWithNamedStringsWrapperTODO is a placeholder for code generation. See WrapperTODO for details.

type CallWithNamedStringsWrapperFunc

type CallWithNamedStringsWrapperFunc func(ctx context.Context, args map[string]string) (results []any, err error)

CallWithNamedStringsWrapperFunc is a function type that implements CallWithNamedStringsWrapper. It allows any function with the matching signature to be used as a CallWithNamedStringsWrapper.

func (CallWithNamedStringsWrapperFunc) CallWithNamedStrings

func (f CallWithNamedStringsWrapperFunc) CallWithNamedStrings(ctx context.Context, args map[string]string) (results []any, err error)

type CallWithStringsWrapper

type CallWithStringsWrapper interface {
	CallWithStrings(ctx context.Context, args ...string) (results []any, err error)
}

CallWithStringsWrapper provides the ability to call a function with string arguments that are automatically converted to the required types. String conversion supports basic types, time values, slices, structs (as JSON), and more. See StringScanner for conversion details.

func CallWithStringsWrapperTODO

func CallWithStringsWrapperTODO(function any) CallWithStringsWrapper

CallWithStringsWrapperTODO is a placeholder for code generation. See WrapperTODO for details.

type CallWithStringsWrapperFunc

type CallWithStringsWrapperFunc func(ctx context.Context, args ...string) (results []any, err error)

CallWithStringsWrapperFunc is a function type that implements CallWithStringsWrapper. It allows any function with the matching signature to be used as a CallWithStringsWrapper.

func (CallWithStringsWrapperFunc) CallWithStrings

func (f CallWithStringsWrapperFunc) CallWithStrings(ctx context.Context, args ...string) (results []any, err error)

type CallWrapper

type CallWrapper interface {
	Call(ctx context.Context, args []any) (results []any, err error)
}

CallWrapper provides the ability to call a function with a slice of any values. The context is passed as the first argument if the function accepts context.Context. Arguments are type-checked and converted as needed before calling the wrapped function.

func CallWrapperTODO

func CallWrapperTODO(function any) CallWrapper

CallWrapperTODO is a placeholder for code generation. See WrapperTODO for details.

type CallWrapperFunc

type CallWrapperFunc func(ctx context.Context, args []any) (results []any, err error)

CallWrapperFunc is a function type that implements CallWrapper. It allows any function with the matching signature to be used as a CallWrapper.

func (CallWrapperFunc) Call

func (f CallWrapperFunc) Call(ctx context.Context, args []any) (results []any, err error)

type Description

type Description interface {
	// Name returns the function name.
	Name() string

	// String returns a string representation of the function signature.
	String() string

	// NumArgs returns the total number of arguments (including context if present).
	NumArgs() int

	// ContextArg returns true if the first argument is context.Context.
	ContextArg() bool

	// NumResults returns the total number of results (including error if present).
	NumResults() int

	// ErrorResult returns true if the last result is an error.
	ErrorResult() bool

	// ArgNames returns the names of all arguments.
	// For generated wrappers, these are the actual parameter names.
	// For reflection-based wrappers, these are generated as "a0", "a1", etc.
	ArgNames() []string

	// ArgDescriptions returns documentation for each argument.
	// This is typically extracted from function comments by code generators.
	ArgDescriptions() []string

	// ArgTypes returns the reflect.Type for each argument.
	ArgTypes() []reflect.Type

	// ResultTypes returns the reflect.Type for each result.
	ResultTypes() []reflect.Type
}

Description provides metadata about a wrapped function including its name, arguments, and results. This interface is used to introspect function signatures and generate documentation, help text, or user interfaces.

func ReflectDescription

func ReflectDescription(name string, f any) (Description, error)

ReflectDescription creates a Description for any function using reflection. The name parameter is used as the function name since reflection cannot determine it. Argument names are generated as "a0", "a1", etc. Returns an error if f is not a function.

Example:

func Add(a, b int) int { return a + b }
desc, err := function.ReflectDescription("Add", Add)
fmt.Println(desc.NumArgs()) // 2
fmt.Println(desc.ArgTypes()[0]) // int

type ErrParseArgJSON

type ErrParseArgJSON struct {
	Err  error
	Func fmt.Stringer
	Arg  string
}

func NewErrParseArgJSON

func NewErrParseArgJSON(err error, f fmt.Stringer, arg string) ErrParseArgJSON

func (ErrParseArgJSON) Error

func (e ErrParseArgJSON) Error() string

func (ErrParseArgJSON) Unwrap

func (e ErrParseArgJSON) Unwrap() error

type ErrParseArgString

type ErrParseArgString struct {
	Err      error
	Func     fmt.Stringer
	ArgName  string
	ArgValue string
}

func NewErrParseArgString

func NewErrParseArgString(err error, f fmt.Stringer, argName, argValue string) ErrParseArgString

func (ErrParseArgString) Error

func (e ErrParseArgString) Error() string

func (ErrParseArgString) Unwrap

func (e ErrParseArgString) Unwrap() error

type ErrParseArgsJSON

type ErrParseArgsJSON struct {
	Err  error
	Func fmt.Stringer
	JSON string
}

func NewErrParseArgsJSON

func NewErrParseArgsJSON(err error, f fmt.Stringer, argsJSON []byte) ErrParseArgsJSON

func (ErrParseArgsJSON) Error

func (e ErrParseArgsJSON) Error() string

func (ErrParseArgsJSON) Unwrap

func (e ErrParseArgsJSON) Unwrap() error

type ErrorFuncWrapper added in v0.2.0

type ErrorFuncWrapper func() error

ErrorFuncWrapper is a Wrapper for a function without arguments that returns only an error. It implements all wrapper interfaces and is useful for validation or initialization functions.

Example:

var ValidateConfig ErrorFuncWrapper = func() error {
    if config == nil { return errors.New("config is nil") }
    return nil
}
_, err := ValidateConfig.Call(ctx, nil)

func (ErrorFuncWrapper) ArgDescriptions added in v0.2.0

func (ErrorFuncWrapper) ArgDescriptions() []string

func (ErrorFuncWrapper) ArgNames added in v0.2.0

func (ErrorFuncWrapper) ArgNames() []string

func (ErrorFuncWrapper) ArgTypes added in v0.2.0

func (ErrorFuncWrapper) ArgTypes() []reflect.Type

func (ErrorFuncWrapper) Call added in v0.2.0

func (f ErrorFuncWrapper) Call(context.Context, []any) ([]any, error)

func (ErrorFuncWrapper) CallWithJSON added in v0.2.0

func (f ErrorFuncWrapper) CallWithJSON(context.Context, []byte) (results []any, err error)

func (ErrorFuncWrapper) CallWithNamedStrings added in v0.2.0

func (f ErrorFuncWrapper) CallWithNamedStrings(context.Context, map[string]string) ([]any, error)

func (ErrorFuncWrapper) CallWithStrings added in v0.2.0

func (f ErrorFuncWrapper) CallWithStrings(context.Context, ...string) ([]any, error)

func (ErrorFuncWrapper) ContextArg added in v0.2.0

func (ErrorFuncWrapper) ContextArg() bool

func (ErrorFuncWrapper) ErrorResult added in v0.2.0

func (ErrorFuncWrapper) ErrorResult() bool

func (ErrorFuncWrapper) Name added in v0.2.0

func (ErrorFuncWrapper) Name() string

func (ErrorFuncWrapper) NumArgs added in v0.2.0

func (ErrorFuncWrapper) NumArgs() int

func (ErrorFuncWrapper) NumResults added in v0.2.0

func (ErrorFuncWrapper) NumResults() int

func (ErrorFuncWrapper) ResultTypes added in v0.2.0

func (ErrorFuncWrapper) ResultTypes() []reflect.Type

func (ErrorFuncWrapper) String added in v0.2.0

func (ErrorFuncWrapper) String() string

type JSONArgsFunc

type JSONArgsFunc func(ctx context.Context, jsonObject []byte) error

func NewJSONArgsFunc

func NewJSONArgsFunc(f Wrapper, resultsHandlers ...ResultsHandler) JSONArgsFunc

type Logger

type Logger interface {
	Printf(format string, args ...any)
}

Logger interface

type NamedStringArgsFunc

type NamedStringArgsFunc func(ctx context.Context, args map[string]string) error

func NewNamedStringArgsFunc

func NewNamedStringArgsFunc(f CallWithNamedStringsWrapper, resultsHandlers ...ResultsHandler) NamedStringArgsFunc

type PrintlnText

type PrintlnText string

PrintlnText prints a fixed string if a command returns without an error

func (PrintlnText) HandleResults

func (t PrintlnText) HandleResults(ctx context.Context, results []any, resultErr error) error

type ResultsHandler

type ResultsHandler interface {
	HandleResults(ctx context.Context, results []any, resultErr error) error
}

type ResultsHandlerFunc

type ResultsHandlerFunc func(ctx context.Context, results []any, resultErr error) error
var PrintStructSliceAsTable ResultsHandlerFunc = func(ctx context.Context, results []any, resultErr error) error {
	if resultErr != nil {
		return resultErr
	}
	if len(results) == 0 {
		return nil
	}
	for i, result := range results {

		resultType := reflect.TypeOf(result)
		if resultType.Kind() != reflect.Slice && resultType.Kind() != reflect.Array {
			return fmt.Errorf("expected slice or array, got %T for result %d", result, i)
		}
		if resultType.Elem().Kind() != reflect.Struct && resultType.Elem().Kind() != reflect.Pointer {
			return fmt.Errorf("expected slice/array of structs or struct pointers, got %T for result %d", result, i)
		}
		isPtr := resultType.Elem().Kind() == reflect.Pointer
		structType := resultType.Elem()
		if isPtr {
			structType = structType.Elem()
		}
		var header []string
		for col := 0; col < structType.NumField(); col++ {
			header = append(header, structType.Field(col).Name)
		}
		rows := [][]string{header}

		sliceVal := reflect.ValueOf(result)
		for row := 0; row < sliceVal.Len(); row++ {
			structVal := sliceVal.Index(row)
			if isPtr {
				if structVal.IsNil() {
					continue
				}
				structVal = structVal.Elem()
			}
			fieldStrings := make([]string, structType.NumField())
			for col := 0; col < structType.NumField(); col++ {
				fieldStrings[col] = fmt.Sprint(structVal.Field(col).Interface())
			}
			rows = append(rows, fieldStrings)
		}

		colWidths := make([]int, structType.NumField())
		for row := range rows {
			for col := 0; col < structType.NumField() && col < len(rows[row]); col++ {
				numRunes := utf8.RuneCountInString(rows[row][col])
				colWidths[col] = max(colWidths[col], numRunes)
			}
		}

		if i > 0 {
			fmt.Println()
		}
		w := os.Stdout
		var err error
		for _, rowStrs := range rows {
			for col, colWidth := range colWidths {
				switch {
				case col == 0:
					_, err = w.Write([]byte("| "))
				case col < len(colWidths):
					_, err = w.Write([]byte(" | "))
				}
				if err != nil {
					return err
				}
				str := ""
				if col < len(rowStrs) {
					str = rowStrs[col]
				}
				_, err = io.WriteString(w, str)
				if err != nil {
					return err
				}
				strLen := utf8.RuneCountInString(str)
				for i := strLen; i < colWidth; i++ {
					_, err = w.Write([]byte{' '})
					if err != nil {
						return err
					}
				}
			}
			_, err = w.Write([]byte(" |\n"))
			if err != nil {
				return err
			}
		}
	}
	return nil
}

PrintStructSliceAsTable prints a slice of structs or struct pointers as a padded table to os.Stdout using fmt.Sprint to format field values.

var Println ResultsHandlerFunc = func(ctx context.Context, results []any, resultErr error) error {
	if resultErr != nil {
		return resultErr
	}
	err := makeResultsPrintable(results)
	if err != nil {
		return err
	}
	for _, r := range results {
		_, err = fmt.Println(r)
		if err != nil {
			return err
		}
	}
	return nil
}

Println calls fmt.Println for every result

func LogTo

func LogTo(logger Logger) ResultsHandlerFunc

LogTo calls logger.Printf(fmt.Sprintln(results...))

func LogWithPrefixTo

func LogWithPrefixTo(prefix string, logger Logger) ResultsHandlerFunc

LogWithPrefixTo calls logger.Printf(fmt.Sprintln(results...)) with prefix prepended to the results

func PrintTo

func PrintTo(writer io.Writer) ResultsHandlerFunc

PrintTo calls fmt.Fprint on writer with the result values as varidic arguments

func PrintlnTo

func PrintlnTo(writer io.Writer) ResultsHandlerFunc

PrintlnTo calls fmt.Fprintln on writer for every result

func PrintlnWithPrefix

func PrintlnWithPrefix(prefix string) ResultsHandlerFunc

PrintlnWithPrefix calls fmt.Println(prefix, result) for every result value

func PrintlnWithPrefixTo

func PrintlnWithPrefixTo(prefix string, writer io.Writer) ResultsHandlerFunc

PrintlnWithPrefixTo calls fmt.Fprintln(writer, prefix, result) for every result value

func (ResultsHandlerFunc) HandleResults

func (f ResultsHandlerFunc) HandleResults(ctx context.Context, results []any, resultErr error) error

type StringArgsFunc

type StringArgsFunc func(ctx context.Context, args ...string) error

func NewStringArgsFunc

func NewStringArgsFunc(f CallWithStringsWrapper, resultsHandlers ...ResultsHandler) StringArgsFunc

type StringScanner

type StringScanner interface {
	ScanString(sourceStr string, destPtr any) error
}

StringScanner defines the interface for converting strings to typed values. Custom implementations can be registered via TypeStringScanners to add support for additional types or override default conversion behavior.

type StringScannerFunc

type StringScannerFunc func(sourceStr string, destPtr any) error

StringScannerFunc is a function type that implements StringScanner. It allows standalone functions to be used as StringScanners.

func (StringScannerFunc) ScanString

func (f StringScannerFunc) ScanString(sourceStr string, destPtr any) error

type TypeStringScanners

type TypeStringScanners struct {
	Types      map[reflect.Type]StringScanner
	Interfaces map[reflect.Type]StringScanner
	Kinds      map[reflect.Kind]StringScanner
	Default    StringScanner
}

func NewTypeStringScanners

func NewTypeStringScanners(defaultScanner StringScanner) *TypeStringScanners

func (*TypeStringScanners) ScanString

func (s *TypeStringScanners) ScanString(sourceStr string, destPtr any) error

func (*TypeStringScanners) WithDefaultScanner

func (s *TypeStringScanners) WithDefaultScanner(scanner StringScanner) *TypeStringScanners

func (*TypeStringScanners) WithInterfaceTypeScanner

func (s *TypeStringScanners) WithInterfaceTypeScanner(destImplsInterface reflect.Type, scanner StringScanner) *TypeStringScanners

func (*TypeStringScanners) WithKindScanner

func (s *TypeStringScanners) WithKindScanner(destKind reflect.Kind, scanner StringScanner) *TypeStringScanners

func (*TypeStringScanners) WithTypeScanner

func (s *TypeStringScanners) WithTypeScanner(destType reflect.Type, scanner StringScanner) *TypeStringScanners

type VoidFuncWrapper added in v0.2.0

type VoidFuncWrapper func()

VoidFuncWrapper is a Wrapper for a function without arguments and without results. It implements all wrapper interfaces and can be used for simple callback-style functions.

Example:

var PrintHello VoidFuncWrapper = func() { fmt.Println("Hello") }
PrintHello.Call(ctx, nil) // Prints "Hello"

func (VoidFuncWrapper) ArgDescriptions added in v0.2.0

func (VoidFuncWrapper) ArgDescriptions() []string

func (VoidFuncWrapper) ArgNames added in v0.2.0

func (VoidFuncWrapper) ArgNames() []string

func (VoidFuncWrapper) ArgTypes added in v0.2.0

func (VoidFuncWrapper) ArgTypes() []reflect.Type

func (VoidFuncWrapper) Call added in v0.2.0

func (f VoidFuncWrapper) Call(context.Context, []any) ([]any, error)

func (VoidFuncWrapper) CallWithJSON added in v0.2.0

func (f VoidFuncWrapper) CallWithJSON(context.Context, []byte) (results []any, err error)

func (VoidFuncWrapper) CallWithNamedStrings added in v0.2.0

func (f VoidFuncWrapper) CallWithNamedStrings(context.Context, map[string]string) ([]any, error)

func (VoidFuncWrapper) CallWithStrings added in v0.2.0

func (f VoidFuncWrapper) CallWithStrings(context.Context, ...string) ([]any, error)

func (VoidFuncWrapper) ContextArg added in v0.2.0

func (VoidFuncWrapper) ContextArg() bool

func (VoidFuncWrapper) ErrorResult added in v0.2.0

func (VoidFuncWrapper) ErrorResult() bool

func (VoidFuncWrapper) Name added in v0.2.0

func (VoidFuncWrapper) Name() string

func (VoidFuncWrapper) NumArgs added in v0.2.0

func (VoidFuncWrapper) NumArgs() int

func (VoidFuncWrapper) NumResults added in v0.2.0

func (VoidFuncWrapper) NumResults() int

func (VoidFuncWrapper) ResultTypes added in v0.2.0

func (VoidFuncWrapper) ResultTypes() []reflect.Type

func (VoidFuncWrapper) String added in v0.2.0

func (VoidFuncWrapper) String() string

type Wrapper

Wrapper is the main interface for wrapped functions, providing multiple calling conventions. It combines Description (function metadata), CallWrapper (direct calls), CallWithStringsWrapper (string argument parsing), CallWithNamedStringsWrapper (named arguments), and CallWithJSONWrapper (JSON argument parsing).

Wrapper implementations can be created either through reflection using ReflectWrapper, or through code generation using gen-func-wrappers for better performance.

func MustReflectWrapper

func MustReflectWrapper(function any, argNames ...string) Wrapper

MustReflectWrapper calls ReflectWrapper and panics any error.

func ReflectWrapper

func ReflectWrapper(function any, argNames ...string) (Wrapper, error)

ReflectWrapper returns a Wrapper for the passed function using reflection and the passed argNames. The number of passed argNames must match the number of function arguments. Except when the function only has one argument of type context.Context then "ctx" is assumed as argument name in case no name has been passed.

func WrapperTODO

func WrapperTODO(function any) Wrapper

WrapperTODO is a placeholder function used during development with code generation. It should be replaced by running gen-func-wrappers, which generates optimized wrapper code without reflection overhead.

Usage:

var MyFuncWrapper = function.WrapperTODO(MyFunc)

Then run: go run github.com/domonda/go-function/cmd/gen-func-wrappers

This will replace WrapperTODO calls with generated wrapper implementations. The function panics at runtime to ensure you don't forget to run the code generator.

Directories

Path Synopsis
cli module
cmd
htmlform module
Package httpfun provides utilities for creating HTTP handlers from wrapped functions.
Package httpfun provides utilities for creating HTTP handlers from wrapped functions.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL