| package commonhttp |
|
|
| import ( |
| "bytes" |
| "context" |
| "encoding/csv" |
| "encoding/json" |
| "fmt" |
| "mime" |
| "net/http" |
| "net/url" |
|
|
| "github.com/openmeterio/openmeter/pkg/framework/transport/httptransport/encoder" |
| "github.com/openmeterio/openmeter/pkg/models" |
| ) |
|
|
| |
| |
| func GetMediaType(r *http.Request) (string, error) { |
| var err error |
|
|
| |
| accept := r.Header.Get("Accept") |
| if accept == "" { |
| accept = "application/json" |
| } |
|
|
| mediatype, _, err := mime.ParseMediaType(accept) |
| |
| |
| if err != nil { |
| err = fmt.Errorf("invalid media type, default to json: %w", err) |
| mediatype = "application/json" |
| } |
|
|
| return mediatype, err |
| } |
|
|
| |
| func JSONResponseEncoder[Response any](_ context.Context, w http.ResponseWriter, _ *http.Request, response Response) error { |
| return jsonResponseEncoder(w, http.StatusOK, response) |
| } |
|
|
| |
| func jsonResponseEncoder[Response any](w http.ResponseWriter, statusCode int, response Response) error { |
| buf := &bytes.Buffer{} |
| enc := json.NewEncoder(buf) |
| enc.SetEscapeHTML(true) |
|
|
| if err := enc.Encode(response); err != nil { |
| return err |
| } |
|
|
| w.Header().Set("Content-Type", "application/json") |
| w.WriteHeader(statusCode) |
|
|
| _, err := w.Write(buf.Bytes()) |
| if err != nil { |
| return err |
| } |
|
|
| return nil |
| } |
|
|
| func JSONResponseEncoderWithStatus[Response any](statusCode int) encoder.ResponseEncoder[Response] { |
| return func(ctx context.Context, w http.ResponseWriter, _ *http.Request, response Response) error { |
| return jsonResponseEncoder(w, statusCode, response) |
| } |
| } |
|
|
| |
| func PlainTextResponseEncoder[Response string](_ context.Context, w http.ResponseWriter, _ *http.Request, response Response) error { |
| return plainTextResponseEncoder(w, http.StatusOK, response) |
| } |
|
|
| |
| func plainTextResponseEncoder[Response string](w http.ResponseWriter, statusCode int, response Response) error { |
| w.Header().Set("Content-Type", "text/plain") |
| w.WriteHeader(statusCode) |
|
|
| _, err := w.Write([]byte(response)) |
| if err != nil { |
| return err |
| } |
|
|
| return nil |
| } |
|
|
| |
| type CSVResponse interface { |
| FileName() string |
| Records() [][]string |
| } |
|
|
| |
| func CSVResponseEncoder[Response CSVResponse](_ context.Context, w http.ResponseWriter, _ *http.Request, response Response) error { |
| return csvResponseEncoder(w, http.StatusOK, response) |
| } |
|
|
| |
| func csvResponseEncoder[Response CSVResponse](w http.ResponseWriter, statusCode int, response Response) error { |
| buf := &bytes.Buffer{} |
| writer := csv.NewWriter(buf) |
| if err := writer.WriteAll(response.Records()); err != nil { |
| return fmt.Errorf("writing record to csv: %w", err) |
| } |
| if err := writer.Error(); err != nil { |
| return fmt.Errorf("writing csv: %w", err) |
| } |
|
|
| w.Header().Set("Content-Type", "text/csv") |
| w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.csv", response.FileName())) |
| w.WriteHeader(statusCode) |
|
|
| if _, err := w.Write(buf.Bytes()); err != nil { |
| return err |
| } |
| return nil |
| } |
|
|
| func EmptyResponseEncoder[Response any](statusCode int) encoder.ResponseEncoder[Response] { |
| return func(_ context.Context, w http.ResponseWriter, _ *http.Request, resp Response) error { |
| w.WriteHeader(statusCode) |
| return nil |
| } |
| } |
|
|
| |
| func DummyErrorEncoder() encoder.ErrorEncoder { |
| return func(ctx context.Context, err error, w http.ResponseWriter, _ *http.Request) bool { |
| NewHTTPError(http.StatusBadRequest, err).EncodeError(ctx, w) |
| return true |
| } |
| } |
|
|
| |
| func GenericErrorEncoder() encoder.ErrorEncoder { |
| return func(ctx context.Context, err error, w http.ResponseWriter, r *http.Request) bool { |
| return HandleIssueIfHTTPStatusKnown(ctx, err, w) || |
| HandleErrorIfTypeMatches[*models.GenericConflictError](ctx, http.StatusConflict, err, w) || |
| HandleErrorIfTypeMatches[*models.GenericForbiddenError](ctx, http.StatusForbidden, err, w) || |
| HandleErrorIfTypeMatches[*models.GenericNotImplementedError](ctx, http.StatusNotImplemented, err, w) || |
| HandleErrorIfTypeMatches[*models.GenericValidationError](ctx, http.StatusBadRequest, err, w) || |
| HandleErrorIfTypeMatches[*models.GenericNotFoundError](ctx, http.StatusNotFound, err, w) || |
| HandleErrorIfTypeMatches[*models.GenericUnauthorizedError](ctx, http.StatusUnauthorized, err, w) || |
| HandleErrorIfTypeMatches[*models.GenericPreConditionFailedError](ctx, http.StatusPreconditionFailed, err, w) |
| } |
| } |
|
|
| func RedirectResponseEncoder[Response string](statusCode int) encoder.ResponseEncoder[Response] { |
| return func(ctx context.Context, w http.ResponseWriter, r *http.Request, response Response) error { |
| redirectURL := string(response) |
|
|
| _, err := url.Parse(redirectURL) |
| if err != nil { |
| return fmt.Errorf("invalid redirect url: %w", err) |
| } |
|
|
| if statusCode < 300 || statusCode > 399 { |
| return fmt.Errorf("invalid redirect status code: it must be in 3xx range: %w", err) |
| } |
|
|
| http.Redirect(w, r, redirectURL, statusCode) |
|
|
| return nil |
| } |
| } |
|
|