Skip to content

Commit

Permalink
feat: switch ContentType from options pattern to fluent
Browse files Browse the repository at this point in the history
  • Loading branch information
rvagg committed Sep 7, 2023
1 parent 7777929 commit 83d1e83
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 86 deletions.
51 changes: 26 additions & 25 deletions http/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,19 @@ var (
ResponseChunkDelimeter = []byte("0\r\n") // An http/1.1 chunk delimeter, used for specifying an early end to the response
)

// ContentType represents a Content-Type descriptor for use with the response
// Content-Type header or the request Accept header specifically for
// Trustless Gateway requests and responses.
type ContentType struct {
Mime string
MimeType string
Order ContentTypeOrder
Duplicates bool
Quality float32
}

func (ct ContentType) String() string {
sb := strings.Builder{}
sb.WriteString(ct.Mime)
sb.WriteString(ct.MimeType)
sb.WriteString(";version=")
sb.WriteString(MimeTypeCarVersion)
sb.WriteString(";order=")
Expand All @@ -51,37 +54,37 @@ func (ct ContentType) String() string {
return sb.String()
}

type ContentTypeOption func(ct *ContentType)
// WithOrder returns a new ContentType with the specified order.
func (ct ContentType) WithOrder(order ContentTypeOrder) ContentType {
ct.Order = order
return ct
}

func WithContentTypeOrder(order ContentTypeOrder) ContentTypeOption {
return func(ct *ContentType) {
ct.Order = order
}
// WithDuplicates returns a new ContentType with the specified duplicates.
func (ct ContentType) WithDuplicates(duplicates bool) ContentType {
ct.Duplicates = duplicates
return ct
}

func WithContentTypeDuplicates(duplicates bool) ContentTypeOption {
return func(ct *ContentType) {
ct.Duplicates = duplicates
}
// WithMime returns a new ContentType with the specified mime type.
func (ct ContentType) WithMimeType(mime string) ContentType {
ct.MimeType = mime
return ct
}

func WithContentTypeQuality(quality float32) ContentTypeOption {
return func(ct *ContentType) {
ct.Quality = quality
}
// WithQuality returns a new ContentType with the specified quality.
func (ct ContentType) WithQuality(quality float32) ContentType {
ct.Quality = quality
return ct
}

func NewContentType(opt ...ContentTypeOption) ContentType {
ct := ContentType{
Mime: MimeTypeCar,
func DefaultContentType() ContentType {
return ContentType{
MimeType: MimeTypeCar,
Order: DefaultOrder,
Duplicates: DefaultIncludeDupes,
Quality: 1,
}
for _, o := range opt {
o(&ct)
}
return ct
}

// ResponseContentTypeHeader returns the value for the Content-Type header for a
Expand All @@ -90,9 +93,7 @@ func NewContentType(opt ...ContentTypeOption) ContentType {
//
// Deprecated: Use NewContentType().String() instead.
func ResponseContentTypeHeader(duplicates bool) string {
ct := NewContentType()
ct.Duplicates = duplicates
return ct.String()
return DefaultContentType().WithDuplicates(duplicates).String()
}

// RequestAcceptHeader returns the value for the Accept header for a Trustless
Expand Down
12 changes: 6 additions & 6 deletions http/constants_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ func TestContentType(t *testing.T) {
req.Equal("application/vnd.ipld.car;version=1;order=dfs;dups=n", trustlesshttp.ResponseContentTypeHeader(false))
req.Equal("application/vnd.ipld.car;version=1;order=dfs;dups=n", trustlesshttp.RequestAcceptHeader(false))

req.Equal("application/vnd.ipld.car;version=1;order=dfs;dups=y", trustlesshttp.NewContentType().String())
req.Equal("application/vnd.ipld.car;version=1;order=dfs;dups=y;q=0.8", trustlesshttp.NewContentType(trustlesshttp.WithContentTypeQuality(0.8)).String())
req.Equal("application/vnd.ipld.car;version=1;order=dfs;dups=y;q=0.333", trustlesshttp.NewContentType(trustlesshttp.WithContentTypeQuality(1.0/3.0)).String())
req.Equal("application/vnd.ipld.car;version=1;order=dfs;dups=y", trustlesshttp.NewContentType(trustlesshttp.WithContentTypeQuality(-1.0)).String())
req.Equal("application/vnd.ipld.car;version=1;order=dfs;dups=n", trustlesshttp.NewContentType(trustlesshttp.WithContentTypeDuplicates(false)).String())
req.Equal("application/vnd.ipld.car;version=1;order=unk;dups=n", trustlesshttp.NewContentType(trustlesshttp.WithContentTypeDuplicates(false), trustlesshttp.WithContentTypeOrder(trustlesshttp.ContentTypeOrderUnk)).String())
req.Equal("application/vnd.ipld.car;version=1;order=dfs;dups=y", trustlesshttp.DefaultContentType().String())
req.Equal("application/vnd.ipld.car;version=1;order=dfs;dups=y;q=0.8", trustlesshttp.DefaultContentType().WithQuality(0.8).String())
req.Equal("application/vnd.ipld.car;version=1;order=dfs;dups=y;q=0.333", trustlesshttp.DefaultContentType().WithQuality(1.0/3.0).String())
req.Equal("application/vnd.ipld.car;version=1;order=dfs;dups=y", trustlesshttp.DefaultContentType().WithQuality(-1.0).String())
req.Equal("application/vnd.ipld.car;version=1;order=dfs;dups=n", trustlesshttp.DefaultContentType().WithDuplicates(false).String())
req.Equal("application/vnd.ipld.car;version=1;order=unk;dups=n", trustlesshttp.DefaultContentType().WithDuplicates(false).WithOrder(trustlesshttp.ContentTypeOrderUnk).String())
}
5 changes: 2 additions & 3 deletions http/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func CheckFormat(req *http.Request) (ContentType, error) {
}

if validFormat {
return NewContentType(), nil // default is acceptable in this case (no accept but format=car)
return DefaultContentType(), nil // default is acceptable in this case (no accept but format=car)
}

return ContentType{}, fmt.Errorf("neither a valid Accept header nor format parameter were provided")
Expand Down Expand Up @@ -140,8 +140,7 @@ func parseContentType(header string, strictType bool) (ContentType, bool) {
typeParts := strings.Split(header, ";")
mime := strings.TrimSpace(typeParts[0])
if mime == MimeTypeCar || (!strictType && (mime == "*/*" || mime == "application/*")) {
contentType := NewContentType()
contentType.Mime = mime
contentType := DefaultContentType().WithMimeType(mime)
// parse additional car attributes outlined in IPIP-412
// https://specs.ipfs.tech/http-gateways/trustless-gateway/
for _, nextPart := range typeParts[1:] {
Expand Down
104 changes: 52 additions & 52 deletions http/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,15 @@ func TestCheckFormat(t *testing.T) {
err string
}{
{"empty (err)", "", "", trustlesshttp.ContentType{}, "neither a valid Accept header nor format parameter were provided"},
{"format=bop (err)", "", "format=bop", trustlesshttp.NewContentType(), "invalid format parameter; unsupported: \"bop\""},
{"format=car", "", "format=car", trustlesshttp.NewContentType(), ""},
{"plain accept", "application/vnd.ipld.car", "", trustlesshttp.NewContentType(), ""},
{"accept dups", "application/vnd.ipld.car; dups=y", "", trustlesshttp.NewContentType(), ""},
{"accept no dups", "application/vnd.ipld.car; dups=n", "", trustlesshttp.NewContentType(trustlesshttp.WithContentTypeDuplicates(false)), ""},
{"accept no dups and cruft", "application/vnd.ipld.car; dups=n; bip; bop", "", trustlesshttp.NewContentType(trustlesshttp.WithContentTypeDuplicates(false)), ""},
{"valid accept but format=bop (err)", "application/vnd.ipld.car; dups=y", "format=bop", trustlesshttp.NewContentType(), "invalid format parameter; unsupported: \"bop\""},
{"valid accept but format=car", "application/vnd.ipld.car; dups=y", "format=car", trustlesshttp.NewContentType(), ""},
{"invalid accept but format=car", "application/vnd.ipld.car; dups=YES!", "format=car", trustlesshttp.NewContentType(trustlesshttp.WithContentTypeDuplicates(false)), "invalid Accept header; unsupported"},
{"format=bop (err)", "", "format=bop", trustlesshttp.DefaultContentType(), "invalid format parameter; unsupported: \"bop\""},
{"format=car", "", "format=car", trustlesshttp.DefaultContentType(), ""},
{"plain accept", "application/vnd.ipld.car", "", trustlesshttp.DefaultContentType(), ""},
{"accept dups", "application/vnd.ipld.car; dups=y", "", trustlesshttp.DefaultContentType(), ""},
{"accept no dups", "application/vnd.ipld.car; dups=n", "", trustlesshttp.DefaultContentType().WithDuplicates(false), ""},
{"accept no dups and cruft", "application/vnd.ipld.car; dups=n; bip; bop", "", trustlesshttp.DefaultContentType().WithDuplicates(false), ""},
{"valid accept but format=bop (err)", "application/vnd.ipld.car; dups=y", "format=bop", trustlesshttp.DefaultContentType(), "invalid format parameter; unsupported: \"bop\""},
{"valid accept but format=car", "application/vnd.ipld.car; dups=y", "format=car", trustlesshttp.DefaultContentType(), ""},
{"invalid accept but format=car", "application/vnd.ipld.car; dups=YES!", "format=car", trustlesshttp.DefaultContentType().WithDuplicates(false), "invalid Accept header; unsupported"},
} {
t.Run(tc.name, func(t *testing.T) {
req := &http.Request{}
Expand All @@ -143,32 +143,32 @@ func TestCheckFormat(t *testing.T) {

func TestParseContentType(t *testing.T) {
for _, tc := range []struct {
name string
accept string
expectValidContentType bool
expectDups bool
name string
accept string
expectValid bool
expectContentType trustlesshttp.ContentType
}{
{"empty (err)", "", false, false},
{"plain", "application/vnd.ipld.car", true, true},
{"*/*", "*/*", false, false},
{"application/*", "application/*", false, false},
{"dups", "application/vnd.ipld.car; dups=y", true, true},
{"no dups", "application/vnd.ipld.car; dups=n", true, false},
{"no dups and cruft", "application/vnd.ipld.car; dups=n; bip; bop", true, false},
{"version=1", "application/vnd.ipld.car; version=1; dups=n", true, false},
{"version=2", "application/vnd.ipld.car; version=2; dups=n", false, false},
{"order=dfs", "application/vnd.ipld.car; order=dfs; dups=n", true, false},
{"order=unk", "application/vnd.ipld.car; order=unk; dups=n", true, false},
{"order=bork", "application/vnd.ipld.car; order=bork; dups=y", false, false},
{"complete", "application/vnd.ipld.car; order=dfs; dups=y; version=1", true, true},
{"complete (squish)", "application/vnd.ipld.car;order=dfs;dups=y;version=1", true, true},
{"complete (shuffle)", "application/vnd.ipld.car;version=1;dups=y;order=dfs;", true, true},
{"complete (cruft)", "application/vnd.ipld.car;;version=1; bip ; dups=n ;bop;order=dfs;--", true, false},
{"empty (err)", "", false, trustlesshttp.ContentType{}},
{"plain", "application/vnd.ipld.car", true, trustlesshttp.DefaultContentType()},
{"*/*", "*/*", false, trustlesshttp.ContentType{}},
{"application/*", "application/*", false, trustlesshttp.ContentType{}},
{"dups", "application/vnd.ipld.car; dups=y", true, trustlesshttp.DefaultContentType()},
{"no dups", "application/vnd.ipld.car; dups=n", true, trustlesshttp.DefaultContentType().WithDuplicates(false)},
{"no dups and cruft", "application/vnd.ipld.car; dups=n; bip; bop", true, trustlesshttp.DefaultContentType().WithDuplicates(false)},
{"version=1", "application/vnd.ipld.car; version=1; dups=n", true, trustlesshttp.DefaultContentType().WithDuplicates(false)},
{"version=2", "application/vnd.ipld.car; version=2; dups=n", false, trustlesshttp.ContentType{}},
{"order=dfs", "application/vnd.ipld.car; order=dfs; dups=n", true, trustlesshttp.DefaultContentType().WithDuplicates(false)},
{"order=unk", "application/vnd.ipld.car; order=unk; dups=n", true, trustlesshttp.DefaultContentType().WithDuplicates(false).WithOrder(trustlesshttp.ContentTypeOrderUnk)},
{"order=bork", "application/vnd.ipld.car; order=bork; dups=y", false, trustlesshttp.ContentType{}},
{"complete", "application/vnd.ipld.car; order=dfs; dups=y; version=1", true, trustlesshttp.DefaultContentType()},
{"complete (squish)", "application/vnd.ipld.car;order=dfs;dups=y;version=1", true, trustlesshttp.DefaultContentType()},
{"complete (shuffle)", "application/vnd.ipld.car;version=1;dups=y;order=dfs;", true, trustlesshttp.DefaultContentType()},
{"complete (cruft)", "application/vnd.ipld.car;;version=1; bip ; dups=n ;bop;order=dfs;--", true, trustlesshttp.DefaultContentType().WithDuplicates(false)},
} {
t.Run(tc.name, func(t *testing.T) {
valid, dups := trustlesshttp.ParseContentType(tc.accept)
require.Equal(t, tc.expectValidContentType, valid)
require.Equal(t, tc.expectDups, dups)
ct, valid := trustlesshttp.ParseContentType(tc.accept)
require.Equal(t, tc.expectValid, valid)
require.Equal(t, tc.expectContentType, ct)
})
}
}
Expand All @@ -180,35 +180,35 @@ func TestParseAccept(t *testing.T) {
expected []trustlesshttp.ContentType
}{
{"empty (err)", "", []trustlesshttp.ContentType{}},
{"plain", "application/vnd.ipld.car", []trustlesshttp.ContentType{{Mime: trustlesshttp.MimeTypeCar, Duplicates: true, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"*/*", "*/*", []trustlesshttp.ContentType{{Mime: "*/*", Duplicates: true, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"application/*", "application/*", []trustlesshttp.ContentType{{Mime: "application/*", Duplicates: true, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"dups", "application/vnd.ipld.car; dups=y", []trustlesshttp.ContentType{{Mime: trustlesshttp.MimeTypeCar, Duplicates: true, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"no dups", "application/vnd.ipld.car; dups=n", []trustlesshttp.ContentType{{Mime: trustlesshttp.MimeTypeCar, Duplicates: false, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"no dups and cruft", "application/vnd.ipld.car; dups=n; bip; bop", []trustlesshttp.ContentType{{Mime: trustlesshttp.MimeTypeCar, Duplicates: false, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"version=1", "application/vnd.ipld.car; version=1; dups=n", []trustlesshttp.ContentType{{Mime: trustlesshttp.MimeTypeCar, Duplicates: false, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"plain", "application/vnd.ipld.car", []trustlesshttp.ContentType{{MimeType: trustlesshttp.MimeTypeCar, Duplicates: true, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"*/*", "*/*", []trustlesshttp.ContentType{{MimeType: "*/*", Duplicates: true, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"application/*", "application/*", []trustlesshttp.ContentType{{MimeType: "application/*", Duplicates: true, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"dups", "application/vnd.ipld.car; dups=y", []trustlesshttp.ContentType{{MimeType: trustlesshttp.MimeTypeCar, Duplicates: true, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"no dups", "application/vnd.ipld.car; dups=n", []trustlesshttp.ContentType{{MimeType: trustlesshttp.MimeTypeCar, Duplicates: false, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"no dups and cruft", "application/vnd.ipld.car; dups=n; bip; bop", []trustlesshttp.ContentType{{MimeType: trustlesshttp.MimeTypeCar, Duplicates: false, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"version=1", "application/vnd.ipld.car; version=1; dups=n", []trustlesshttp.ContentType{{MimeType: trustlesshttp.MimeTypeCar, Duplicates: false, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"version=2", "application/vnd.ipld.car; version=2; dups=n", []trustlesshttp.ContentType{}},
{"order=dfs", "application/vnd.ipld.car; order=dfs; dups=n", []trustlesshttp.ContentType{{Mime: trustlesshttp.MimeTypeCar, Duplicates: false, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"order=unk", "application/vnd.ipld.car; order=unk; dups=n", []trustlesshttp.ContentType{{Mime: trustlesshttp.MimeTypeCar, Duplicates: false, Order: trustlesshttp.ContentTypeOrderUnk, Quality: 1.0}}},
{"order=dfs", "application/vnd.ipld.car; order=dfs; dups=n", []trustlesshttp.ContentType{{MimeType: trustlesshttp.MimeTypeCar, Duplicates: false, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"order=unk", "application/vnd.ipld.car; order=unk; dups=n", []trustlesshttp.ContentType{{MimeType: trustlesshttp.MimeTypeCar, Duplicates: false, Order: trustlesshttp.ContentTypeOrderUnk, Quality: 1.0}}},
{"order=bork", "application/vnd.ipld.car; order=bork; dups=y", []trustlesshttp.ContentType{}},
{"complete", "application/vnd.ipld.car; order=dfs; dups=y; version=1", []trustlesshttp.ContentType{{Mime: trustlesshttp.MimeTypeCar, Duplicates: true, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"complete (squish)", "application/vnd.ipld.car;order=dfs;dups=y;version=1", []trustlesshttp.ContentType{{Mime: trustlesshttp.MimeTypeCar, Duplicates: true, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"complete (shuffle)", "application/vnd.ipld.car;version=1;dups=y;order=dfs;", []trustlesshttp.ContentType{{Mime: trustlesshttp.MimeTypeCar, Duplicates: true, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"complete (cruft)", "application/vnd.ipld.car;;version=1; bip ; dups=n ;bop;order=dfs;--", []trustlesshttp.ContentType{{Mime: trustlesshttp.MimeTypeCar, Duplicates: false, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"q", "application/vnd.ipld.car; order=dfs; q=0.77; dups=n", []trustlesshttp.ContentType{{Mime: trustlesshttp.MimeTypeCar, Duplicates: false, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 0.77}}},
{"complete", "application/vnd.ipld.car; order=dfs; dups=y; version=1", []trustlesshttp.ContentType{{MimeType: trustlesshttp.MimeTypeCar, Duplicates: true, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"complete (squish)", "application/vnd.ipld.car;order=dfs;dups=y;version=1", []trustlesshttp.ContentType{{MimeType: trustlesshttp.MimeTypeCar, Duplicates: true, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"complete (shuffle)", "application/vnd.ipld.car;version=1;dups=y;order=dfs;", []trustlesshttp.ContentType{{MimeType: trustlesshttp.MimeTypeCar, Duplicates: true, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"complete (cruft)", "application/vnd.ipld.car;;version=1; bip ; dups=n ;bop;order=dfs;--", []trustlesshttp.ContentType{{MimeType: trustlesshttp.MimeTypeCar, Duplicates: false, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0}}},
{"q", "application/vnd.ipld.car; order=dfs; q=0.77; dups=n", []trustlesshttp.ContentType{{MimeType: trustlesshttp.MimeTypeCar, Duplicates: false, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 0.77}}},
{"q=bork", "application/vnd.ipld.car; order=dfs; q=bork; dups=n", []trustlesshttp.ContentType{}},
{"q=-1", "application/vnd.ipld.car; order=dfs; q=-0.1; dups=n", []trustlesshttp.ContentType{}},

{
"ordered",
"application/vnd.ipld.car;dups=n;order=unk;q=0.8, text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.1, application/vnd.ipld.car;dups=y;order=dfs;q=0.9 , application/vnd.ipld.car, application/vnd.ipld.car;dups=y;order=unk;q=0.7, application/vnd.ipld.car;dups=y;order=dfs;q=0.7",
[]trustlesshttp.ContentType{
{Mime: trustlesshttp.MimeTypeCar, Duplicates: true, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0},
{Mime: trustlesshttp.MimeTypeCar, Duplicates: true, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 0.9},
{Mime: trustlesshttp.MimeTypeCar, Duplicates: false, Order: trustlesshttp.ContentTypeOrderUnk, Quality: 0.8},
{Mime: trustlesshttp.MimeTypeCar, Duplicates: true, Order: trustlesshttp.ContentTypeOrderUnk, Quality: 0.7},
{Mime: trustlesshttp.MimeTypeCar, Duplicates: true, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 0.7},
{Mime: "*/*", Duplicates: true, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 0.1},
{MimeType: trustlesshttp.MimeTypeCar, Duplicates: true, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 1.0},
{MimeType: trustlesshttp.MimeTypeCar, Duplicates: true, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 0.9},
{MimeType: trustlesshttp.MimeTypeCar, Duplicates: false, Order: trustlesshttp.ContentTypeOrderUnk, Quality: 0.8},
{MimeType: trustlesshttp.MimeTypeCar, Duplicates: true, Order: trustlesshttp.ContentTypeOrderUnk, Quality: 0.7},
{MimeType: trustlesshttp.MimeTypeCar, Duplicates: true, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 0.7},
{MimeType: "*/*", Duplicates: true, Order: trustlesshttp.ContentTypeOrderDfs, Quality: 0.1},
},
},
} {
Expand Down

0 comments on commit 83d1e83

Please sign in to comment.