builder.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. package multipartestutils
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io"
  6. "mime/multipart"
  7. "net/http"
  8. "net/http/httptest"
  9. "net/url"
  10. "os"
  11. "path/filepath"
  12. )
  13. // Builder is a multipart builder.
  14. // It is not thread-safe.
  15. type Builder struct {
  16. pageURL string
  17. eb eventBody
  18. queries url.Values
  19. cbs []func(*multipart.Writer) error
  20. }
  21. // New constructs new multipart Builder.
  22. func NewMultipartBuilder() *Builder {
  23. return &Builder{
  24. queries: url.Values{},
  25. }
  26. }
  27. // AddField adds multipart field.
  28. func (b *Builder) AddField(fieldName, value string) *Builder {
  29. b.cbs = append(b.cbs, func(mw *multipart.Writer) error {
  30. if err := mw.WriteField(fieldName, value); err != nil {
  31. return fmt.Errorf("multipartbuilder: failed to write field %s=%s: %s", fieldName, value, err.Error())
  32. }
  33. return nil
  34. })
  35. return b
  36. }
  37. // AddReader adds multipart file field from provided reader.
  38. func (b *Builder) AddReader(fieldName, fileName string, reader io.Reader) *Builder {
  39. b.cbs = append(b.cbs, func(mw *multipart.Writer) error {
  40. w, err := mw.CreateFormFile(fieldName, fileName)
  41. if err != nil {
  42. return fmt.Errorf("multipartbuilder: failed to create form file %s (%s) for reader: %s", fieldName, fileName, err.Error())
  43. }
  44. _, err = io.Copy(w, reader)
  45. if err != nil {
  46. return fmt.Errorf("multipartbuilder: failed to copy form file %s (%s) for reader: %s", fieldName, fileName, err.Error())
  47. }
  48. return nil
  49. })
  50. return b
  51. }
  52. // AddFile adds multipart file field from specified file path.
  53. func (b *Builder) AddFile(fieldName, filePath string) *Builder {
  54. b.cbs = append(b.cbs, func(mw *multipart.Writer) error {
  55. f, err := os.Open(filePath)
  56. if err != nil {
  57. return fmt.Errorf("multipartbuilder: failed to open file %s (%s): %s", fieldName, filePath, err.Error())
  58. }
  59. defer f.Close()
  60. w, err := mw.CreateFormFile(fieldName, filepath.Base(filePath))
  61. if err != nil {
  62. return fmt.Errorf("multipartbuilder: failed to create form file %s (%s): %s", fieldName, filePath, err.Error())
  63. }
  64. _, err = io.Copy(w, f)
  65. if err != nil {
  66. return fmt.Errorf("multipartbuilder: failed to copy form file %s (%s): %s", fieldName, filePath, err.Error())
  67. }
  68. return nil
  69. })
  70. return b
  71. }
  72. // Build finalizes Builder, returning Content-Type and multipart reader.
  73. // It should be called only once for Builder.
  74. // Returned reader should be used (Read/Close) at least once to clean up properly.
  75. // Any errors are bound to returned reader (will be returned on Read/Close).
  76. func (b *Builder) Build() (string, io.ReadCloser) {
  77. r, w := io.Pipe()
  78. mw := multipart.NewWriter(w)
  79. go func() {
  80. for _, cb := range b.cbs {
  81. if err := cb(mw); err != nil {
  82. _ = w.CloseWithError(err)
  83. return
  84. }
  85. }
  86. _ = w.CloseWithError(mw.Close())
  87. }()
  88. return mw.FormDataContentType(), r
  89. }
  90. type EventFuncID struct {
  91. ID string `json:"id,omitempty"`
  92. Params []string `json:"params,omitempty"`
  93. }
  94. type Event struct {
  95. Checked bool `json:"checked,omitempty"` // For Checkbox
  96. From string `json:"from,omitempty"` // For DatePicker
  97. To string `json:"to,omitempty"` // For DatePicker
  98. Value string `json:"value,omitempty"` // For Input, DatePicker
  99. }
  100. type eventBody struct {
  101. Event Event `json:"event,omitempty"`
  102. }
  103. func (b *Builder) EventFunc(id string) *Builder {
  104. b.queries["__execute_event__"] = []string{id}
  105. return b
  106. }
  107. func (b *Builder) Query(key string, value string) *Builder {
  108. b.queries[key] = []string{value}
  109. return b
  110. }
  111. func (b *Builder) Queries(v url.Values) *Builder {
  112. b.queries = v
  113. return b
  114. }
  115. func (b *Builder) Event(evt Event) *Builder {
  116. b.eb.Event = evt
  117. return b
  118. }
  119. func (b *Builder) PageURL(url string) *Builder {
  120. b.pageURL = url
  121. return b
  122. }
  123. func (b *Builder) BuildEventFuncRequest() (r *http.Request) {
  124. eventBody, _ := json.Marshal(b.eb)
  125. b.AddField("__event_data__", string(eventBody))
  126. contentType, body := b.Build()
  127. pu := b.pageURL
  128. if len(b.pageURL) == 0 {
  129. pu = "/"
  130. }
  131. r = httptest.NewRequest("POST", fmt.Sprintf("%s?%s", pu, b.queries.Encode()), body)
  132. r.Header.Add("Content-Type", contentType)
  133. return
  134. }