mirror of
https://github.com/safing/portbase
synced 2025-09-01 18:19:57 +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
|
@ -5,8 +5,8 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"mime"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTP Related Errors.
|
// 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)
|
return 0, fmt.Errorf("dsd: failed to read http body: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get mime type from header, then check, clean and verify it.
|
// Load depending on mime type.
|
||||||
if mimeType == "" {
|
return MimeLoad(data, mimeType, t)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestHTTPResponseFormat sets the Accept header to the given format.
|
// RequestHTTPResponseFormat sets the Accept header to the given format.
|
||||||
|
@ -61,11 +48,6 @@ func RequestHTTPResponseFormat(r *http.Request, format uint8) (mimeType string,
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", ErrIncompatibleFormat
|
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.
|
// Request response format.
|
||||||
r.Header.Set("Accept", mimeType)
|
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
|
// DumpToHTTPRequest dumps the given data to the HTTP request using the given
|
||||||
// format. It also sets the Accept header to the same format.
|
// format. It also sets the Accept header to the same format.
|
||||||
func DumpToHTTPRequest(r *http.Request, t interface{}, format uint8) error {
|
func DumpToHTTPRequest(r *http.Request, t interface{}, format uint8) error {
|
||||||
|
// Get mime type and set request format.
|
||||||
mimeType, err := RequestHTTPResponseFormat(r, format)
|
mimeType, err := RequestHTTPResponseFormat(r, format)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
return fmt.Errorf("dsd: failed to serialize: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set body.
|
// Add data to request.
|
||||||
r.Header.Set("Content-Type", mimeType)
|
r.Header.Set("Content-Type", mimeType)
|
||||||
r.Body = io.NopCloser(bytes.NewReader(data))
|
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
|
// DumpToHTTPResponse dumpts the given data to the HTTP response, using the
|
||||||
// format defined in the request's Accept header.
|
// format defined in the request's Accept header.
|
||||||
func DumpToHTTPResponse(w http.ResponseWriter, r *http.Request, t interface{}) error {
|
func DumpToHTTPResponse(w http.ResponseWriter, r *http.Request, t interface{}) error {
|
||||||
// Get format from Accept header.
|
// Serialize data based on accept header.
|
||||||
// TODO: Improve parsing of Accept header.
|
data, mimeType, _, err := MimeDump(t, r.Header.Get("Accept"))
|
||||||
mimeType := r.Header.Get("Accept")
|
|
||||||
format, ok := MimeTypeToFormat[mimeType]
|
|
||||||
if !ok {
|
|
||||||
return ErrIncompatibleFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize data.
|
|
||||||
data, err := dumpWithoutIdentifier(t, format, "")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("dsd: failed to serialize: %w", err)
|
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
|
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.
|
// Format and MimeType mappings.
|
||||||
var (
|
var (
|
||||||
FormatToMimeType = map[uint8]string{
|
FormatToMimeType = map[uint8]string{
|
||||||
JSON: "application/json; charset=utf-8",
|
|
||||||
CBOR: "application/cbor",
|
CBOR: "application/cbor",
|
||||||
|
JSON: "application/json",
|
||||||
MsgPack: "application/msgpack",
|
MsgPack: "application/msgpack",
|
||||||
|
YAML: "application/yaml",
|
||||||
}
|
}
|
||||||
MimeTypeToFormat = map[string]uint8{
|
MimeTypeToFormat = map[string]uint8{
|
||||||
"application/json": JSON,
|
|
||||||
"application/cbor": CBOR,
|
"application/cbor": CBOR,
|
||||||
|
"application/json": JSON,
|
||||||
"application/msgpack": MsgPack,
|
"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