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.
- 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
go get github.com/domonda/go-function
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
}
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
}
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
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.
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
)
Wrapper
- Main interface for wrapped functionsDescription
- Function metadata (name, arguments, types)StringScanner
- String-to-type conversionReflectWrapper
- Reflection-based wrapperWrapperTODO
- Placeholder for code generation
NewHandler
- Create HTTP handler from functionHandlerWithoutContext
- HTTP handler without context argRequestArgs
- Parse function arguments from HTTP requestsResultsWriter
- Write function results as HTTP responses
StringArgsDispatcher
- Command dispatcher for CLI appsSuperStringArgsDispatcher
- Multi-level command dispatcherComplete
- Shell completion support
NewHandler
- Generate HTML forms for functions- Form generation with validation
- Customizable templates
- Generates optimized wrapper code
- Zero reflection overhead
- Preserves type safety
- Supports custom import prefixes
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",
}
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
// 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)
// 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:]...)
The library includes comprehensive tests. Run them with:
go test ./...
Contributions are welcome! Please:
- Add tests for new features
- Update documentation
- Follow existing code style
- Ensure
go test ./...
passes
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