github.com/moov-io/imagecashletter@v0.10.1/internal/responder/responder.go (about) 1 package responder 2 3 import ( 4 "encoding/json" 5 "net/http" 6 7 "github.com/moov-io/base/log" 8 "github.com/moov-io/imagecashletter" 9 client "github.com/moov-io/imagecashletter/client" 10 ) 11 12 // Responder is a helper for writing responses to an http.ResponseWriter. 13 type Responder struct { 14 logger log.Logger 15 w http.ResponseWriter 16 r *http.Request 17 optionalHeaders 18 } 19 20 type optionalHeaders struct { 21 location string 22 } 23 24 func (h optionalHeaders) apply(w http.ResponseWriter) { 25 if h.location != "" { 26 w.Header().Set("Location", h.location) 27 } 28 } 29 30 func NewResponder(logger log.Logger, w http.ResponseWriter, r *http.Request) *Responder { 31 return &Responder{ 32 logger: logger, 33 w: w, 34 r: r, 35 } 36 } 37 38 // WithLocation sets the Location header on the response. The Location header should be used for 39 // HTTP 201 responses to indicate the location of the newly created resource. While both absolute 40 // and relative URIs are allowed, the absolute URI is preferred. 41 func (r *Responder) WithLocation(location string) *Responder { 42 r.optionalHeaders.location = location 43 return r 44 } 45 46 // File writes the file to the http.ResponseWriter as an attachment. It should 47 // only be called when the request's Accept header was "application/octet-stream" or "text/plain". 48 func (r *Responder) File(status int, file imagecashletter.File, name string) { 49 opts := []imagecashletter.WriterOption{ 50 imagecashletter.WriteVariableLineLengthOption(), 51 } 52 53 // determine which encoding the caller expects to set up the writer and response headers 54 mimeType := r.r.Header.Get("Accept") 55 switch mimeType { 56 case "application/octet-stream": 57 r.w.Header().Set("Content-Type", "application/octet-stream") 58 opts = append(opts, imagecashletter.WriteEbcdicEncodingOption()) 59 case "text/plain": 60 r.w.Header().Set("Content-Type", "text/plain") 61 default: 62 r.logger.LogErrorf("renderer: file method called for unsupported mime-type: %s", mimeType) 63 r.w.WriteHeader(http.StatusInternalServerError) 64 return 65 } 66 67 r.optionalHeaders.apply(r.w) 68 r.w.Header().Set("Content-Disposition", "attachment; filename="+name) 69 r.w.WriteHeader(status) 70 71 if err := imagecashletter.NewWriter(r.w, opts...).Write(&file); err != nil { 72 r.logger.LogErrorf("rendering file: %v", err) 73 r.w.WriteHeader(http.StatusInternalServerError) 74 return 75 } 76 } 77 78 // JSON writes the resource to the http.ResponseWriter as JSON. 79 func (r *Responder) JSON(status int, resource any) { 80 r.optionalHeaders.apply(r.w) 81 r.w.Header().Set("Content-Type", "application/json; charset=UTF-8") 82 r.w.WriteHeader(status) 83 if err := json.NewEncoder(r.w).Encode(resource); err != nil { 84 r.logger.LogErrorf("problem encoding response: %v", err) 85 r.w.WriteHeader(http.StatusInternalServerError) 86 return 87 } 88 } 89 90 // Error sets the status code and writes an error to the http.ResponseWriter. Response body is 91 // omitted for 5xx errors. 92 func (r *Responder) Error(status int, err error) { 93 if status >= 500 { // don't return body for internal errors 94 r.w.WriteHeader(status) 95 return 96 } 97 98 r.w.Header().Set("Content-Type", "application/json; charset=UTF-8") 99 r.w.WriteHeader(status) 100 if err := json.NewEncoder(r.w).Encode(client.Error{Error: err.Error()}); err != nil { 101 r.logger.LogErrorf("problem encoding response: %v", err) 102 r.w.WriteHeader(http.StatusInternalServerError) 103 return 104 } 105 }