-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
notice.go
147 lines (130 loc) · 5.65 KB
/
notice.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package sprout
import (
"fmt"
"strings"
"github.com/go-sprout/sprout/internal/runtime"
)
// NoticeKind represents the type of notice that can be applied to a function.
// It is an enumeration with different possible values that dictate how the
// notice should behave.
type NoticeKind int
type FunctionNotice struct {
// FunctionNames is a list of function names to which the notice should be
// applied. The function names are case-sensitive.
FunctionNames []string
// Kind is the kind of the notice
Kind NoticeKind
// Message is the message of the notice
Message string
}
const (
// NoticeKindDeprecated indicates that the function is deprecated.
NoticeKindDeprecated NoticeKind = iota + 1
// NoticeKindInfo indicates that the notice is informational.
NoticeKindInfo
// NoticeKindDebug indicates that the notice is for debugging purposes.
// When using this kind, the notice message can contain the "$out" placeholder
// which will be replaced with the output of the function.
NoticeKindDebug
)
// NewNotice creates a new function notice with the given function names, kind,
// and message. The function names are case-sensitive. The kind should be one of
// the predefined NoticeKind values. The message is a string that describes the
// notice.
//
// Example:
//
// notice := NewNotice(NoticeKindDeprecated, []string{"myFunc"}, "please use myNewFunc instead")
//
// This example creates a new notice that indicates the function "myFunc" is
// deprecated and should be replaced with "myNewFunc" during template rendering.
func NewNotice(kind NoticeKind, functionNames []string, message string) *FunctionNotice {
return &FunctionNotice{
FunctionNames: functionNames,
Kind: kind,
Message: message,
}
}
// NewDeprecatedNotice creates a new deprecated function notice with the given
// function name and message. The function name is case-sensitive. The message
// is a string that describes what the user should do instead of using the
// deprecated function.
func NewDeprecatedNotice(functionName, message string) *FunctionNotice {
return NewNotice(NoticeKindDeprecated, []string{functionName}, message)
}
// NewInfoNotice creates a new informational function notice with the given
// function name and message. The function name is case-sensitive. The message
// is a string that provides additional information.
func NewInfoNotice(functionName, message string) *FunctionNotice {
return NewNotice(NoticeKindInfo, []string{functionName}, message)
}
// NewDebugNotice creates a new debug function notice with the given function
// name and message. The function name is case-sensitive. The message is a
// string that provides additional information for debugging purposes. The
// message can contain the "$out" placeholder, which will be replaced with the
// output of the function.
func NewDebugNotice(functionName, message string) *FunctionNotice {
return NewNotice(NoticeKindDebug, []string{functionName}, message)
}
// AssignNotices assigns all notices defined in the handler to their original
// functions. This function is used to ensure that all notices are properly
// associated with their original functions in the handler instance.
//
// It should be called after all functions and notices have been added and
// inside the Build function in case of using a custom handler.
func AssignNotices(h Handler) {
funcs := h.RawFunctions()
for _, notice := range h.Notices() {
for _, functionName := range notice.FunctionNames {
if fn, ok := funcs[functionName]; ok {
wrappedFn := noticeWrapper(h, notice, functionName, fn)
funcs[functionName] = wrappedFn
}
}
}
}
// noticeWrapper creates a wrapped function that logs a notice after
// calling the original function. The notice is logged using the handler's
// logger instance. The wrapped function is returned as a wrappedFunc, which
// is a type alias for a function that takes a variadic list of arguments
// and returns an `any` result and an `error`.
func noticeWrapper(h Handler, notice FunctionNotice, functionName string, fn any) wrappedFunction {
return func(args ...any) (any, error) {
out, err := runtime.SafeCall(fn, args...)
switch notice.Kind {
case NoticeKindDebug:
h.Logger().With("function", functionName, "notice", "debug").Debug(strings.ReplaceAll(notice.Message, "$out", fmt.Sprint(out)))
case NoticeKindInfo:
h.Logger().With("function", functionName, "notice", "info").Info(notice.Message)
case NoticeKindDeprecated:
h.Logger().With("function", functionName, "notice", "deprecated").Warn(fmt.Sprintf("Template function `%s` is deprecated: %s", functionName, notice.Message))
}
return out, err
}
}
// WithNotices is used to add one or more function notices to the handler.
// This option allows you to associate a notice with a function, providing
// information about the function's deprecation or other special handling.
//
// The notices are applied to the original function name and its aliases.
// You can use the ApplyOnAliases method on the FunctionNotice to control
// whether the notice should be applied to aliases.
func WithNotices(notices ...*FunctionNotice) HandlerOption[*DefaultHandler] {
return func(p *DefaultHandler) error {
// Preallocate the slice if we expect to append multiple notices
if cap(p.notices) < len(p.notices)+len(notices) {
newNotices := make([]FunctionNotice, len(p.notices), len(p.notices)+len(notices))
copy(newNotices, p.notices)
p.notices = newNotices
}
for _, notice := range notices {
// Skip if the function name is empty or the kind is not valid
if len(notice.FunctionNames) == 0 || notice.Kind <= 0 {
continue
}
// Append the notice directly without dereferencing
p.notices = append(p.notices, *notice)
}
return nil
}
}