/dal/formatters.go

https://github.com/ghetzel/pivot · Go · 287 lines · 225 code · 51 blank · 11 comment · 55 complexity · 77e78a72a34f66f4bb301316bcedb7fa MD5 · raw file

  1. package dal
  2. import (
  3. "encoding/base32"
  4. "encoding/base64"
  5. "encoding/hex"
  6. "fmt"
  7. "regexp"
  8. "strings"
  9. "time"
  10. "github.com/ghetzel/go-stockutil/sliceutil"
  11. "github.com/ghetzel/go-stockutil/stringutil"
  12. "github.com/ghetzel/go-stockutil/typeutil"
  13. "github.com/jbenet/go-base58"
  14. )
  15. type EncoderFunc func([]byte) (string, error) //{}
  16. var Base32Encoder = func(src []byte) (string, error) {
  17. return strings.TrimSuffix(base32.StdEncoding.EncodeToString(src), `=`), nil
  18. }
  19. var Base58Encoder = func(src []byte) (string, error) {
  20. return base58.Encode(src), nil
  21. }
  22. var Base64Encoder = func(src []byte) (string, error) {
  23. return strings.TrimSuffix(base64.StdEncoding.EncodeToString(src), `=`), nil
  24. }
  25. var HexEncoder = func(src []byte) (string, error) {
  26. return hex.EncodeToString(src), nil
  27. }
  28. func FormatterFromMap(in map[string]interface{}) (FieldFormatterFunc, error) {
  29. formatters := make([]FieldFormatterFunc, 0)
  30. for name, defn := range in {
  31. if formatter, err := GetFormatter(name, defn); err == nil {
  32. formatters = append(formatters, formatter)
  33. } else {
  34. return nil, fmt.Errorf("Invalid formatter configuration %v: %v", name, err)
  35. }
  36. }
  37. return FormatAll(formatters...), nil
  38. }
  39. func GetFormatter(name string, args interface{}) (FieldFormatterFunc, error) {
  40. switch name {
  41. case `uuid`:
  42. return GenerateUUID, nil
  43. case `encoded-uuid`:
  44. var encoder EncoderFunc
  45. switch fmt.Sprintf("%v", args) {
  46. case `base32`:
  47. encoder = Base32Encoder
  48. case `base58`:
  49. encoder = Base58Encoder
  50. case `base64`:
  51. encoder = Base64Encoder
  52. default:
  53. encoder = HexEncoder
  54. }
  55. return GenerateEncodedUUID(encoder), nil
  56. case `trim-space`:
  57. return TrimSpace, nil
  58. case `change-case`:
  59. return ChangeCase(sliceutil.Stringify(args)...), nil
  60. case `replace`:
  61. return Replace(sliceutil.Sliceify(args)), nil
  62. // case `fields`:
  63. // if typeutil.IsMap(args) {
  64. // return DeriveFromFields()
  65. // }
  66. case `current-time`:
  67. return CurrentTime, nil
  68. case `current-time-if-unset`:
  69. return CurrentTimeIfUnset, nil
  70. case `now-plus-duration`:
  71. return NowPlusDuration(typeutil.V(args).Duration()), nil
  72. default:
  73. return nil, fmt.Errorf("Unknown formatter %q", name)
  74. }
  75. }
  76. func FormatAll(formatters ...FieldFormatterFunc) FieldFormatterFunc {
  77. return func(value interface{}, op FieldOperation) (interface{}, error) {
  78. for _, formatter := range formatters {
  79. if v, err := formatter(value, op); err == nil {
  80. value = v
  81. } else {
  82. return nil, err
  83. }
  84. }
  85. return value, nil
  86. }
  87. }
  88. func ChangeCase(cases ...string) FieldFormatterFunc {
  89. return func(value interface{}, _ FieldOperation) (interface{}, error) {
  90. if record, ok := value.(*Record); ok {
  91. value = record.ID
  92. }
  93. if vStr, err := stringutil.ToString(value); err == nil {
  94. for _, c := range cases {
  95. switch c {
  96. case `upper`:
  97. vStr = strings.ToUpper(vStr)
  98. case `lower`:
  99. vStr = strings.ToLower(vStr)
  100. case `camelize`:
  101. vStr = stringutil.Camelize(vStr)
  102. case `hyphenate`:
  103. vStr = stringutil.Hyphenate(vStr)
  104. case `underscore`:
  105. vStr = stringutil.Underscore(vStr)
  106. case `title`:
  107. vStr = strings.Title(vStr)
  108. default:
  109. return nil, fmt.Errorf("Unsupported case '%s'", c)
  110. }
  111. }
  112. return vStr, nil
  113. } else {
  114. return value, err
  115. }
  116. }
  117. }
  118. func Replace(pairs []interface{}) FieldFormatterFunc {
  119. return func(value interface{}, _ FieldOperation) (interface{}, error) {
  120. if vStr, err := stringutil.ToString(value); err == nil {
  121. for _, pair := range pairs {
  122. p := sliceutil.Stringify(pair)
  123. if len(p) != 2 {
  124. return nil, fmt.Errorf("'replace' formatter requires an argument of [[FINDPATTERN, REPLACEWITH], ..]")
  125. } else {
  126. find := p[0]
  127. replace := p[1]
  128. if record, ok := value.(*Record); ok {
  129. value = record.ID
  130. }
  131. if rx, err := regexp.Compile(find); err == nil {
  132. vStr = rx.ReplaceAllString(vStr, replace)
  133. } else {
  134. return value, fmt.Errorf("invalid find pattern: %v", err)
  135. }
  136. }
  137. }
  138. return vStr, nil
  139. } else {
  140. return value, err
  141. }
  142. }
  143. }
  144. func TrimSpace(value interface{}, _ FieldOperation) (interface{}, error) {
  145. if record, ok := value.(*Record); ok {
  146. value = record.ID
  147. }
  148. if vStr, err := stringutil.ToString(value); err == nil {
  149. return strings.TrimSpace(vStr), nil
  150. } else {
  151. return value, err
  152. }
  153. }
  154. // Generates a V4 UUID value if the existing value is empty.
  155. func GenerateUUID(value interface{}, _ FieldOperation) (interface{}, error) {
  156. if record, ok := value.(*Record); ok {
  157. value = record.ID
  158. }
  159. if typeutil.IsZero(value) {
  160. value = stringutil.UUID().String()
  161. }
  162. return value, nil
  163. }
  164. // Same as GenerateUUID, but allows for a custom representation of the underlying bytes.
  165. func GenerateEncodedUUID(encoder EncoderFunc) FieldFormatterFunc {
  166. return func(value interface{}, _ FieldOperation) (interface{}, error) {
  167. if record, ok := value.(*Record); ok {
  168. value = record.ID
  169. }
  170. if typeutil.IsZero(value) {
  171. if v, err := encoder(stringutil.UUID().Bytes()); err == nil {
  172. if typeutil.IsZero(v) {
  173. return value, fmt.Errorf("UUID encoder produced a zero-length result")
  174. }
  175. value = v
  176. } else {
  177. return value, err
  178. }
  179. }
  180. return value, nil
  181. }
  182. }
  183. // Only evaluates the given formatter if the current value of the field is empty.
  184. func IfUnset(onlyIf FieldFormatterFunc) FieldFormatterFunc {
  185. return func(value interface{}, op FieldOperation) (interface{}, error) {
  186. if onlyIf != nil {
  187. if typeutil.IsZero(value) {
  188. return onlyIf(value, op)
  189. }
  190. }
  191. return value, nil
  192. }
  193. }
  194. // Extracts values from the given Record and generates a deterministic output based on those values.
  195. func DeriveFromFields(format string, fields ...string) FieldFormatterFunc {
  196. return func(input interface{}, _ FieldOperation) (interface{}, error) {
  197. if record, ok := input.(*Record); ok {
  198. values := make([]interface{}, len(fields))
  199. for i, field := range fields {
  200. values[i] = record.Get(field)
  201. }
  202. return fmt.Sprintf(format, values...), nil
  203. } else {
  204. return nil, fmt.Errorf("DeriveFromFields formatter requires a *dal.Record argument, got %T", input)
  205. }
  206. }
  207. }
  208. // Returns the current time every time the field is persisted.
  209. func CurrentTime(value interface{}, op FieldOperation) (interface{}, error) {
  210. if op == PersistOperation {
  211. return time.Now(), nil
  212. } else {
  213. return value, nil
  214. }
  215. }
  216. // Returns the current time when the field is persisted if the current value is empty.
  217. func CurrentTimeIfUnset(value interface{}, op FieldOperation) (interface{}, error) {
  218. return IfUnset(func(v interface{}, o FieldOperation) (interface{}, error) {
  219. if o == PersistOperation {
  220. return time.Now(), nil
  221. } else {
  222. return v, nil
  223. }
  224. })(value, op)
  225. }
  226. // Returns the current time with an added offset when the field is persisted.
  227. func NowPlusDuration(duration time.Duration) FieldFormatterFunc {
  228. return func(value interface{}, op FieldOperation) (interface{}, error) {
  229. if op == PersistOperation {
  230. if duration != 0 {
  231. return time.Now().Add(duration), nil
  232. }
  233. }
  234. return value, nil
  235. }
  236. }