mirror of
https://github.com/safing/portbase
synced 2025-09-02 18:50:14 +00:00
165 lines
4.5 KiB
Go
165 lines
4.5 KiB
Go
package dsd
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// HTTP Related Errors.
|
|
var (
|
|
ErrMissingBody = errors.New("dsd: missing http body")
|
|
ErrMissingContentType = errors.New("dsd: missing http content type")
|
|
)
|
|
|
|
const (
|
|
httpHeaderContentType = "Content-Type"
|
|
)
|
|
|
|
// LoadFromHTTPRequest loads the data from the body into the given interface.
|
|
func LoadFromHTTPRequest(r *http.Request, t interface{}) (format uint8, err error) {
|
|
return loadFromHTTP(r.Body, r.Header.Get(httpHeaderContentType), t)
|
|
}
|
|
|
|
// LoadFromHTTPResponse loads the data from the body into the given interface.
|
|
// Closing the body is left to the caller.
|
|
func LoadFromHTTPResponse(resp *http.Response, t interface{}) (format uint8, err error) {
|
|
return loadFromHTTP(resp.Body, resp.Header.Get(httpHeaderContentType), t)
|
|
}
|
|
|
|
func loadFromHTTP(body io.Reader, mimeType string, t interface{}) (format uint8, err error) {
|
|
// Read full body.
|
|
data, err := io.ReadAll(body)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("dsd: failed to read http body: %w", err)
|
|
}
|
|
|
|
// Load depending on mime type.
|
|
return MimeLoad(data, mimeType, t)
|
|
}
|
|
|
|
// RequestHTTPResponseFormat sets the Accept header to the given format.
|
|
func RequestHTTPResponseFormat(r *http.Request, format uint8) (mimeType string, err error) {
|
|
// Get mime type.
|
|
mimeType, ok := FormatToMimeType[format]
|
|
if !ok {
|
|
return "", ErrIncompatibleFormat
|
|
}
|
|
|
|
// Request response format.
|
|
r.Header.Set("Accept", mimeType)
|
|
|
|
return mimeType, nil
|
|
}
|
|
|
|
// DumpToHTTPRequest dumps the given data to the HTTP request using the given
|
|
// format. It also sets the Accept header to the same format.
|
|
func DumpToHTTPRequest(r *http.Request, t interface{}, format uint8) error {
|
|
// Get mime type and set request format.
|
|
mimeType, err := RequestHTTPResponseFormat(r, format)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Serialize data.
|
|
data, err := dumpWithoutIdentifier(t, format, "")
|
|
if err != nil {
|
|
return fmt.Errorf("dsd: failed to serialize: %w", err)
|
|
}
|
|
|
|
// Add data to request.
|
|
r.Header.Set("Content-Type", mimeType)
|
|
r.Body = io.NopCloser(bytes.NewReader(data))
|
|
|
|
return nil
|
|
}
|
|
|
|
// DumpToHTTPResponse dumpts the given data to the HTTP response, using the
|
|
// format defined in the request's Accept header.
|
|
func DumpToHTTPResponse(w http.ResponseWriter, r *http.Request, t interface{}) error {
|
|
// Serialize data based on accept header.
|
|
data, mimeType, _, err := MimeDump(t, r.Header.Get("Accept"))
|
|
if err != nil {
|
|
return fmt.Errorf("dsd: failed to serialize: %w", err)
|
|
}
|
|
|
|
// Write data to response
|
|
w.Header().Set("Content-Type", mimeType)
|
|
_, err = w.Write(data)
|
|
if err != nil {
|
|
return fmt.Errorf("dsd: failed to write response: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MimeLoad loads the given data into the interface based on the given mime type.
|
|
func MimeLoad(data []byte, mimeType string, t interface{}) (format uint8, err error) {
|
|
// Find format.
|
|
mimeType = cleanMimeType(mimeType)
|
|
format = MimeTypeToFormat[mimeType]
|
|
if format == 0 {
|
|
return 0, ErrIncompatibleFormat
|
|
}
|
|
|
|
// Load data.
|
|
err = LoadAsFormat(data, format, t)
|
|
return format, err
|
|
}
|
|
|
|
// MimeDump dumps the given interface based on the given mime type accept header.
|
|
func MimeDump(t any, accept string) (data []byte, mimeType string, format uint8, err error) {
|
|
// Find format.
|
|
accept = cleanMimeType(accept)
|
|
switch accept {
|
|
case "", "*", "*/*":
|
|
format = DefaultSerializationFormat
|
|
default:
|
|
format = MimeTypeToFormat[accept]
|
|
if format == 0 {
|
|
return nil, "", 0, ErrIncompatibleFormat
|
|
}
|
|
}
|
|
mimeType = FormatToMimeType[format]
|
|
|
|
// Serialize and return.
|
|
data, err = dumpWithoutIdentifier(t, format, "")
|
|
return data, mimeType, format, err
|
|
}
|
|
|
|
// FormatFromMime returns the format for the given mime type.
|
|
// Will return AUTO format for unsupported or unrecognized mime types.
|
|
func FormatFromMime(mimeType string) (format uint8) {
|
|
return MimeTypeToFormat[cleanMimeType(mimeType)]
|
|
}
|
|
|
|
func cleanMimeType(mimeType string) string {
|
|
if strings.Contains(mimeType, ",") {
|
|
mimeType, _, _ = strings.Cut(mimeType, ",")
|
|
}
|
|
if strings.Contains(mimeType, ";") {
|
|
mimeType, _, _ = strings.Cut(mimeType, ";")
|
|
}
|
|
return mimeType
|
|
}
|
|
|
|
// Format and MimeType mappings.
|
|
var (
|
|
FormatToMimeType = map[uint8]string{
|
|
CBOR: "application/cbor",
|
|
JSON: "application/json",
|
|
MsgPack: "application/msgpack",
|
|
YAML: "application/yaml",
|
|
}
|
|
MimeTypeToFormat = map[string]uint8{
|
|
"application/cbor": CBOR,
|
|
"application/json": JSON,
|
|
"application/msgpack": MsgPack,
|
|
"application/yaml": YAML,
|
|
"text/json": JSON,
|
|
"text/yaml": YAML,
|
|
"text/yml": YAML,
|
|
}
|
|
)
|