Skip to content

Instantly share code, notes, and snippets.

@shakahl
Last active May 27, 2024 14:27
Show Gist options
  • Save shakahl/3d57e9d1089b574995da2d5ccdc313dc to your computer and use it in GitHub Desktop.
Save shakahl/3d57e9d1089b574995da2d5ccdc313dc to your computer and use it in GitHub Desktop.
Go - function that parses key-value map context information from an error string
package main
import (
"errors"
"fmt"
"reflect"
"regexp"
"strings"
)
// ExtendErrorWithContext extends an error with context information.
func ExtendErrorWithContext(err error, context any) error {
if err == nil {
return nil
}
contextStr := extractContext(context)
return fmt.Errorf("%w %s", err, contextStr)
}
// extractContext extracts context information from map, struct, or pointers.
func extractContext(context any) string {
var sb strings.Builder
v := reflect.ValueOf(context)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
switch v.Kind() {
case reflect.Map:
for _, key := range v.MapKeys() {
val := v.MapIndex(key)
fmt.Fprintf(&sb, "%v=%v ", key, val)
}
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
field := v.Type().Field(i)
val := v.Field(i)
fmt.Fprintf(&sb, "%s=%v ", field.Name, val)
}
default:
return ""
}
return strings.TrimSpace(sb.String())
}
// ParseErrorContext parses key-value pairs from an error string and returns them as a nested map.
func ParseErrorContext(e any) map[string]any {
errStr := fmt.Sprint(e)
re := regexp.MustCompile(`(\w+(\.\w+)*)=("[^"]*"|\S+)`)
matches := re.FindAllStringSubmatch(errStr, -1)
result := make(map[string]any)
for _, match := range matches {
keys := strings.Split(match[1], ".")
value := match[3]
if strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`) {
value = value[1 : len(value)-1]
}
// Build the nested map structure
currentMap := result
for i, key := range keys {
if i == len(keys)-1 {
currentMap[key] = value
} else {
if _, exists := currentMap[key]; !exists {
currentMap[key] = make(map[string]any)
}
currentMap = currentMap[key].(map[string]any)
}
}
}
return result
}
// ExtractErrorMessage extracts the original error message without context.
func ExtractErrorMessage(v any) string {
errStr := fmt.Sprint(v)
// Define the regular expression pattern to match key-value pairs.
re := regexp.MustCompile(`((\w+(\.\w+)*)\=.*)$`)
return strings.TrimSpace(re.ReplaceAllString(errStr, ""))
}
// Example struct
type InnerStruct struct {
Level3 string
}
type OuterStruct struct {
Inner *InnerStruct
Code int
}
func main() {
// Example error string with key-value pairs.
errStr := `Error: something went wrong code=500 msg="Internal Server Error" detail="User not found"`
// Parse the error context.
context := ParseErrorContext(errStr)
// Print the parsed context.
for key, value := range context {
fmt.Printf("%s: %s\n", key, value)
}
// Example error
err := errors.New("original error")
// Example context as map
contextMap := map[string]any{
"level1": map[string]any{
"level2": "some_value",
},
}
// Example context as struct
contextStruct := OuterStruct{
Inner: &InnerStruct{
Level3: "struct_value",
},
Code: 500,
}
// Extend error with map context
errWithMapContext := ExtendErrorWithContext(err, contextMap)
fmt.Println(errWithMapContext)
// Outputs: original error level1=map[level2:some_value]
// Extend error with struct context
errWithStructContext := ExtendErrorWithContext(err, contextStruct)
fmt.Println(errWithStructContext)
// Outputs: original error Inner=&{Level3:struct_value} Code=500
// Parse error context
parsedContext := ParseErrorContext(errWithStructContext.Error())
fmt.Println(parsedContext)
// Outputs: map[Code:500 Inner:map[Level3:struct_value]]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment