idempotency.go 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. package protocol
  2. import (
  3. "crypto/rand"
  4. "fmt"
  5. "reflect"
  6. )
  7. // RandReader is the random reader the protocol package will use to read
  8. // random bytes from. This is exported for testing, and should not be used.
  9. var RandReader = rand.Reader
  10. const idempotencyTokenFillTag = `idempotencyToken`
  11. // CanSetIdempotencyToken returns true if the struct field should be
  12. // automatically populated with a Idempotency token.
  13. //
  14. // Only *string and string type fields that are tagged with idempotencyToken
  15. // which are not already set can be auto filled.
  16. func CanSetIdempotencyToken(v reflect.Value, f reflect.StructField) bool {
  17. switch u := v.Interface().(type) {
  18. // To auto fill an Idempotency token the field must be a string,
  19. // tagged for auto fill, and have a zero value.
  20. case *string:
  21. return u == nil && len(f.Tag.Get(idempotencyTokenFillTag)) != 0
  22. case string:
  23. return len(u) == 0 && len(f.Tag.Get(idempotencyTokenFillTag)) != 0
  24. }
  25. return false
  26. }
  27. // GetIdempotencyToken returns a randomly generated idempotency token.
  28. func GetIdempotencyToken() string {
  29. b := make([]byte, 16)
  30. RandReader.Read(b)
  31. return UUIDVersion4(b)
  32. }
  33. // SetIdempotencyToken will set the value provided with a Idempotency Token.
  34. // Given that the value can be set. Will panic if value is not setable.
  35. func SetIdempotencyToken(v reflect.Value) {
  36. if v.Kind() == reflect.Ptr {
  37. if v.IsNil() && v.CanSet() {
  38. v.Set(reflect.New(v.Type().Elem()))
  39. }
  40. v = v.Elem()
  41. }
  42. v = reflect.Indirect(v)
  43. if !v.CanSet() {
  44. panic(fmt.Sprintf("unable to set idempotnecy token %v", v))
  45. }
  46. b := make([]byte, 16)
  47. _, err := rand.Read(b)
  48. if err != nil {
  49. // TODO handle error
  50. return
  51. }
  52. v.Set(reflect.ValueOf(UUIDVersion4(b)))
  53. }
  54. // UUIDVersion4 returns a Version 4 random UUID from the byte slice provided
  55. func UUIDVersion4(u []byte) string {
  56. // https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_.28random.29
  57. // 13th character is "4"
  58. u[6] = (u[6] | 0x40) & 0x4F
  59. // 17th character is "8", "9", "a", or "b"
  60. u[8] = (u[8] | 0x80) & 0xBF
  61. return fmt.Sprintf(`%X-%X-%X-%X-%X`, u[0:4], u[4:6], u[6:8], u[8:10], u[10:])
  62. }