mirror of
https://github.com/safing/portbase
synced 2025-04-17 16:09:08 +00:00
Improve DSD mime type and http utils
This commit is contained in:
parent
277a0ea669
commit
683df179e0
2 changed files with 99 additions and 34 deletions
formats/dsd
|
@ -5,8 +5,8 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// HTTP Related Errors.
|
||||
|
@ -37,21 +37,8 @@ func loadFromHTTP(body io.Reader, mimeType string, t interface{}) (format uint8,
|
|||
return 0, fmt.Errorf("dsd: failed to read http body: %w", err)
|
||||
}
|
||||
|
||||
// Get mime type from header, then check, clean and verify it.
|
||||
if mimeType == "" {
|
||||
return 0, ErrMissingContentType
|
||||
}
|
||||
mimeType, _, err = mime.ParseMediaType(mimeType)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("dsd: failed to parse content type: %w", err)
|
||||
}
|
||||
format, ok := MimeTypeToFormat[mimeType]
|
||||
if !ok {
|
||||
return 0, ErrIncompatibleFormat
|
||||
}
|
||||
|
||||
// Parse data..
|
||||
return format, LoadAsFormat(data, format, t)
|
||||
// Load depending on mime type.
|
||||
return MimeLoad(data, mimeType, t)
|
||||
}
|
||||
|
||||
// RequestHTTPResponseFormat sets the Accept header to the given format.
|
||||
|
@ -61,11 +48,6 @@ func RequestHTTPResponseFormat(r *http.Request, format uint8) (mimeType string,
|
|||
if !ok {
|
||||
return "", ErrIncompatibleFormat
|
||||
}
|
||||
// Omit charset.
|
||||
mimeType, _, err = mime.ParseMediaType(mimeType)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("dsd: failed to parse content type: %w", err)
|
||||
}
|
||||
|
||||
// Request response format.
|
||||
r.Header.Set("Accept", mimeType)
|
||||
|
@ -76,6 +58,7 @@ func RequestHTTPResponseFormat(r *http.Request, format uint8) (mimeType string,
|
|||
// 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
|
||||
|
@ -87,7 +70,7 @@ func DumpToHTTPRequest(r *http.Request, t interface{}, format uint8) error {
|
|||
return fmt.Errorf("dsd: failed to serialize: %w", err)
|
||||
}
|
||||
|
||||
// Set body.
|
||||
// Add data to request.
|
||||
r.Header.Set("Content-Type", mimeType)
|
||||
r.Body = io.NopCloser(bytes.NewReader(data))
|
||||
|
||||
|
@ -97,16 +80,8 @@ func DumpToHTTPRequest(r *http.Request, t interface{}, format uint8) error {
|
|||
// 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 {
|
||||
// Get format from Accept header.
|
||||
// TODO: Improve parsing of Accept header.
|
||||
mimeType := r.Header.Get("Accept")
|
||||
format, ok := MimeTypeToFormat[mimeType]
|
||||
if !ok {
|
||||
return ErrIncompatibleFormat
|
||||
}
|
||||
|
||||
// Serialize data.
|
||||
data, err := dumpWithoutIdentifier(t, format, "")
|
||||
// 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)
|
||||
}
|
||||
|
@ -120,16 +95,71 @@ func DumpToHTTPResponse(w http.ResponseWriter, r *http.Request, t interface{}) e
|
|||
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{
|
||||
JSON: "application/json; charset=utf-8",
|
||||
CBOR: "application/cbor",
|
||||
JSON: "application/json",
|
||||
MsgPack: "application/msgpack",
|
||||
YAML: "application/yaml",
|
||||
}
|
||||
MimeTypeToFormat = map[string]uint8{
|
||||
"application/json": JSON,
|
||||
"application/cbor": CBOR,
|
||||
"application/json": JSON,
|
||||
"application/msgpack": MsgPack,
|
||||
"application/yaml": YAML,
|
||||
"text/json": JSON,
|
||||
"text/yaml": YAML,
|
||||
"text/yml": YAML,
|
||||
}
|
||||
)
|
||||
|
|
35
formats/dsd/http_test.go
Normal file
35
formats/dsd/http_test.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package dsd
|
||||
|
||||
import (
|
||||
"mime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMimeTypes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Test static maps.
|
||||
for _, mimeType := range FormatToMimeType {
|
||||
cleaned, _, err := mime.ParseMediaType(mimeType)
|
||||
assert.NoError(t, err, "mime type must be parse-able")
|
||||
assert.Equal(t, mimeType, cleaned, "mime type should be clean in map already")
|
||||
}
|
||||
for mimeType, _ := range MimeTypeToFormat {
|
||||
cleaned, _, err := mime.ParseMediaType(mimeType)
|
||||
assert.NoError(t, err, "mime type must be parse-able")
|
||||
assert.Equal(t, mimeType, cleaned, "mime type should be clean in map already")
|
||||
}
|
||||
|
||||
// Test assumptions.
|
||||
for mimeType, mimeTypeCleaned := range map[string]string{
|
||||
"application/xml, image/webp": "application/xml",
|
||||
"application/xml;q=0.9, image/webp": "application/xml",
|
||||
"*": "*",
|
||||
"*/*": "*/*",
|
||||
} {
|
||||
cleaned := cleanMimeType(mimeType)
|
||||
assert.Equal(t, mimeTypeCleaned, cleaned, "assumption for %q should hold", mimeType)
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue