package shortcuts import ( "fmt" "maps" "path" "reflect" "strings" "dove/utils/errors" "github.com/gofiber/fiber/v2" ) func Render(context *fiber.Ctx, templateName string, data any) error { templateData := make(fiber.Map) mergeContextValues(context, templateData) if data != nil { if mergeError := mergeBindData(templateData, data); mergeError != nil { return mergeError } } return context.Render(resolveTemplate(context, templateName), templateData) } func RenderWithStatus(context *fiber.Ctx, templateName string, data any, statusCode int) error { context.Status(statusCode) return Render(context, templateName, data) } func resolveTemplate(context *fiber.Ctx, templateName string) string { switch { case context.Get("HX-Request") == "true" && context.Get("HX-Boosted") != "true": directory := path.Dir(templateName) filename := path.Base(templateName) return fmt.Sprintf("%s/htmx/%s.htmx", directory, filename) default: return templateName } } func mergeContextValues(context *fiber.Ctx, targetMap fiber.Map) { context.Context().VisitUserValues(func(key []byte, value any) { targetMap[string(key)] = value }) } func mergeBindData(targetMap fiber.Map, data any) error { normalizedData, normalizeError := normalizeToMap(data) if normalizeError != nil { return normalizeError } maps.Copy(targetMap, normalizedData) return nil } func normalizeToMap(data any) (fiber.Map, error) { switch typedData := data.(type) { case fiber.Map: return typedData, nil case map[string]any: return fiber.Map(typedData), nil default: return convertStructToMap(data) } } func convertStructToMap(data any) (fiber.Map, error) { structValue := reflect.ValueOf(data) switch structValue.Kind() { case reflect.Pointer: structValue = structValue.Elem() } switch structValue.Kind() { case reflect.Struct: return extractStructFields(structValue), nil default: return nil, errors.Error(UnsupportedBindType) } } func extractStructFields(structValue reflect.Value) fiber.Map { structType := structValue.Type() fieldMap := make(fiber.Map, structValue.NumField()) for fieldIndex := range structType.NumField() { fieldDescriptor := structType.Field(fieldIndex) if !fieldDescriptor.IsExported() { continue } fieldKey := resolveFieldKey(fieldDescriptor) fieldMap[fieldKey] = structValue.Field(fieldIndex).Interface() } return fieldMap } func resolveFieldKey(fieldDescriptor reflect.StructField) string { jsonTag := fieldDescriptor.Tag.Get("json") switch { case jsonTag == "" || jsonTag == "-": return fieldDescriptor.Name default: return extractTagName(jsonTag, fieldDescriptor.Name) } } func extractTagName(jsonTag string, fallbackName string) string { separatorIndex := strings.IndexByte(jsonTag, ',') switch { case separatorIndex < 0: return jsonTag case separatorIndex > 0: return jsonTag[:separatorIndex] default: return fallbackName } }