Improve DSD mime type and http utils

This commit is contained in:
Daniel 2023-09-26 12:58:33 +02:00
parent 277a0ea669
commit 683df179e0
2 changed files with 99 additions and 34 deletions

View file

@ -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
View 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)
}
}