gott.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. // SPDX-FileCopyrightText: 2024 Adam Evyčędo
  2. //
  3. // SPDX-License-Identifier: MPL-2.0
  4. package gott
  5. import (
  6. "context"
  7. "fmt"
  8. "log"
  9. "log/slog"
  10. "reflect"
  11. "runtime"
  12. )
  13. type LogLevel int
  14. // LogLevel specifies what to log:
  15. // Quiet logs nothing,
  16. // Error logs only errors,
  17. // Debug logs functions that run,
  18. // Info logs run and skipped functions
  19. const (
  20. Quiet LogLevel = iota
  21. Error
  22. Debug
  23. Info
  24. )
  25. func fnName(fn interface{}) string {
  26. return runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()
  27. }
  28. func logErr(e error, fn interface{}, l *slog.Logger) {
  29. if l != nil {
  30. l.LogAttrs(context.Background(), slog.LevelError, "Function returned error", slog.Attr{Key: "function", Value: slog.StringValue(fnName(fn))}, slog.Attr{Key: "error", Value: slog.StringValue(e.Error())})
  31. } else {
  32. log.Printf("Function %s returned error: %v\n", fnName(fn), e)
  33. }
  34. }
  35. func logMsg(msg string, fn interface{}, l *slog.Logger, level slog.Level) {
  36. if l != nil {
  37. l.LogAttrs(context.Background(), level, msg, slog.Attr{Key: "function", Value: slog.StringValue(fnName(fn))})
  38. } else {
  39. log.Printf("%s: %s\n", msg, fnName(fn))
  40. }
  41. }
  42. // Exception is a type encapsulating anything contained in panic.
  43. // It implements Error() and therefore can be used as error.
  44. type Exception struct {
  45. E interface{}
  46. }
  47. func (e Exception) Error() string {
  48. return fmt.Sprintf("function panicked with %v", e.E)
  49. }
  50. // R is a simplification of Either monad. It’s either succesful—when its error
  51. // is nil—or unsuccessful otherwise.
  52. type R[T any] struct {
  53. S T
  54. E error
  55. LogLevel LogLevel
  56. Logger *slog.Logger
  57. }
  58. // Bind performs f on the receiver’s success value and assigns the returned
  59. // value and error to the receiver if it’s is successful. In either case, Bind
  60. // returns the receiver.
  61. // Bind operates on functions that return value and error.
  62. func (r R[T]) Bind(f func(T) (T, error)) R[T] {
  63. if r.E == nil {
  64. if r.LogLevel >= Debug || r.Logger != nil {
  65. logMsg("running", f, r.Logger, slog.LevelDebug)
  66. }
  67. r.S, r.E = f(r.S)
  68. if r.E != nil {
  69. if r.LogLevel >= Error || r.Logger != nil {
  70. logErr(r.E, f, r.Logger)
  71. }
  72. r.E = fmt.Errorf("while running %s: %w", fnName(f), r.E)
  73. }
  74. } else {
  75. if r.LogLevel >= Info || r.Logger != nil {
  76. logMsg("skipping", f, r.Logger, slog.LevelInfo)
  77. }
  78. }
  79. return r
  80. }
  81. // Map performs f on the receiver’s success value and assigns the returned
  82. // value to the receiver if it’s is successful. In either case, Map returns the
  83. // receiver.
  84. // Map operates on functions that are always successful and return only one
  85. // value.
  86. func (r R[T]) Map(f func(T) T) R[T] {
  87. if r.E == nil {
  88. if r.LogLevel >= Debug || r.Logger != nil {
  89. logMsg("running", f, r.Logger, slog.LevelDebug)
  90. }
  91. r.S = f(r.S)
  92. } else {
  93. if r.LogLevel >= Info || r.Logger != nil {
  94. logMsg("skipping", f, r.Logger, slog.LevelInfo)
  95. }
  96. }
  97. return r
  98. }
  99. // Tee performs f on the receiver’s success value and assigns the returned
  100. // error to the receiver if it’s is successful. In either case, Tee returns the
  101. // receiver.
  102. // Tee operates on functions that only perform side effects and might return an
  103. // error
  104. func (r R[T]) Tee(f func(T) error) R[T] {
  105. if r.E == nil {
  106. if r.LogLevel >= Debug || r.Logger != nil {
  107. logMsg("running", f, r.Logger, slog.LevelDebug)
  108. }
  109. r.E = f(r.S)
  110. if r.E != nil {
  111. if r.LogLevel >= Error || r.Logger != nil {
  112. logErr(r.E, f, r.Logger)
  113. }
  114. r.E = fmt.Errorf("while running %s: %w", fnName(f), r.E)
  115. }
  116. } else {
  117. if r.LogLevel >= Info || r.Logger != nil {
  118. logMsg("skipping", f, r.Logger, slog.LevelInfo)
  119. }
  120. }
  121. return r
  122. }
  123. // SafeTee performs f on the receiver’s success value if the receiver is
  124. // successful. In either case, SafeTee returns the receiver.
  125. // SafeTee operates on functions that only perform side effects and are always
  126. // successful.
  127. func (r R[T]) SafeTee(f func(T)) R[T] {
  128. if r.E == nil {
  129. if r.LogLevel >= Debug || r.Logger != nil {
  130. logMsg("running", f, r.Logger, slog.LevelDebug)
  131. }
  132. f(r.S)
  133. } else {
  134. if r.LogLevel >= Info || r.Logger != nil {
  135. logMsg("skipping", f, r.Logger, slog.LevelInfo)
  136. }
  137. }
  138. return r
  139. }
  140. // Catch performs f on the receiver’s success value and assigns the returned
  141. // vale to the receiver if it’s successful. If f panics, Catch recovers and
  142. // stores the value passed to panic in receiver’s error as Exception. In either
  143. // case, Catch returns the receiver.
  144. func (r R[T]) Catch(f func(T) T) (r2 R[T]) {
  145. r2 = r
  146. if r2.E == nil {
  147. if r2.LogLevel >= Debug || r.Logger != nil {
  148. logMsg("running", f, r.Logger, slog.LevelDebug)
  149. }
  150. defer func() {
  151. if err := recover(); err != nil {
  152. if r2.LogLevel >= Error || r2.Logger != nil {
  153. logErr(Exception{err}, f, r.Logger)
  154. }
  155. r2.E = fmt.Errorf("while running %s: %w", fnName(f), Exception{err})
  156. }
  157. }()
  158. r2.S = f(r.S)
  159. } else {
  160. if r.LogLevel >= Info || r.Logger != nil {
  161. logMsg("skipping", f, r.Logger, slog.LevelInfo)
  162. }
  163. }
  164. return
  165. }
  166. // Revover tries to put processing back on the happy track.
  167. // If receiver is not successful, Recover calls the passed function and
  168. // assignes the returned value and error to the receiver. In either case,
  169. // Recover returns the receiver.
  170. func (r R[T]) Recover(f func(T, error) (T, error)) R[T] {
  171. if r.E != nil {
  172. if r.LogLevel >= Debug || r.Logger != nil {
  173. logMsg("running", f, r.Logger, slog.LevelDebug)
  174. }
  175. r.S, r.E = f(r.S, r.E)
  176. if r.E != nil {
  177. if r.LogLevel >= Error || r.Logger != nil {
  178. logErr(r.E, f, r.Logger)
  179. }
  180. r.E = fmt.Errorf("while running %s: %w", fnName(f), r.E)
  181. }
  182. } else {
  183. if r.LogLevel >= Info || r.Logger != nil {
  184. logMsg("skipping", f, r.Logger, slog.LevelInfo)
  185. }
  186. }
  187. return r
  188. }
  189. // Handle performs onSuccess on the receiver’s success value if the receiver is
  190. // successful, or onError on the receiver’s error otherwise. In either case,
  191. // Handle returns the receiver.
  192. func (r R[T]) Handle(onSuccess func(T), onError func(error)) R[T] {
  193. if r.E == nil {
  194. if r.LogLevel >= Debug || r.Logger != nil {
  195. logMsg("running", onSuccess, r.Logger, slog.LevelDebug)
  196. }
  197. onSuccess(r.S)
  198. } else {
  199. if r.LogLevel >= Debug || r.Logger != nil {
  200. logMsg("running", onError, r.Logger, slog.LevelDebug)
  201. }
  202. onError(r.E)
  203. }
  204. return r
  205. }