Skip to content

Commit

Permalink
feat: Support RFC9110 (#167)
Browse files Browse the repository at this point in the history
* feat: Support RFC9110

* test: Add test cases for content type derivation and validation
  • Loading branch information
samcm committed Jun 11, 2024
1 parent 6e195da commit a2951b0
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 16 deletions.
40 changes: 25 additions & 15 deletions pkg/api/content_type.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package api

import "fmt"
import (
"fmt"
"strings"
)

type ContentType int

Expand All @@ -20,27 +23,34 @@ func (c ContentType) String() string {
case ContentTypeSSZ:
return "application/octet-stream"
case ContentTypeUnknown:
return "unknown"
return "application/unknown"
}

return ""
}

func DeriveContentType(accept string) ContentType {
switch accept {
case "application/json":
return ContentTypeJSON
case "*/*":
return ContentTypeJSON
case "application/yaml":
return ContentTypeYAML
case "application/octet-stream":
return ContentTypeSSZ
// TODO(sam.caldermason): HACK to support Nimbus - what should we do here?
case "application/octet-stream,application/json;q=0.9":
return ContentTypeSSZ
// Split the accept header by commas to handle multiple content types
acceptTypes := strings.Split(accept, ",")
for _, acceptType := range acceptTypes {
// Split each type by semicolon to handle q-values
parts := strings.Split(acceptType, ";")
contentType := strings.TrimSpace(parts[0])

switch contentType {
case "application/json":
return ContentTypeJSON
case "*/*":
return ContentTypeJSON
case "application/yaml":
return ContentTypeYAML
case "application/octet-stream":
return ContentTypeSSZ
}
}

// Default to JSON if they don't care what they get.
case "":
if accept == "" {
return ContentTypeJSON
}

Expand Down
89 changes: 89 additions & 0 deletions pkg/api/content_type_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package api_test

import (
"net/http"
"testing"

"github.com/ethpandaops/checkpointz/pkg/api"
"github.com/stretchr/testify/assert"
)

func TestDeriveContentType(t *testing.T) {
tests := []struct {
name string
accept string
expected api.ContentType
}{
{"JSON", "application/json", api.ContentTypeJSON},
{"Wildcard", "*/*", api.ContentTypeJSON},
{"YAML", "application/yaml", api.ContentTypeYAML},
{"SSZ", "application/octet-stream", api.ContentTypeSSZ},
{"Unknown", "application/unknown", api.ContentTypeUnknown},
{"Empty", "", api.ContentTypeJSON},
{"QValue JSON", "application/json;q=0.8", api.ContentTypeJSON},
{"QValue YAML", "application/yaml;q=0.5", api.ContentTypeYAML},
{"QValue Multiple", "application/json;q=0.8, application/yaml;q=0.5", api.ContentTypeJSON},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := api.DeriveContentType(tt.accept)
assert.Equal(t, tt.expected, result)
})
}
}

func TestValidateContentType(t *testing.T) {
tests := []struct {
name string
contentType api.ContentType
accepting []api.ContentType
expectError bool
}{
{"Valid JSON", api.ContentTypeJSON, []api.ContentType{api.ContentTypeJSON, api.ContentTypeYAML}, false},
{"Invalid JSON", api.ContentTypeJSON, []api.ContentType{api.ContentTypeYAML}, true},
{"Valid YAML", api.ContentTypeYAML, []api.ContentType{api.ContentTypeJSON, api.ContentTypeYAML}, false},
{"Invalid YAML", api.ContentTypeYAML, []api.ContentType{api.ContentTypeJSON}, true},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := api.ValidateContentType(tt.contentType, tt.accepting)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}

func TestNewContentTypeFromRequest(t *testing.T) {
tests := []struct {
name string
accept string
expected api.ContentType
}{
{"JSON", "application/json", api.ContentTypeJSON},
{"Wildcard", "*/*", api.ContentTypeJSON},
{"YAML", "application/yaml", api.ContentTypeYAML},
{"SSZ", "application/octet-stream", api.ContentTypeSSZ},
{"Unknown", "application/unknown", api.ContentTypeJSON},
{"Empty", "", api.ContentTypeJSON},
{"QValue JSON", "application/json;q=0.8", api.ContentTypeJSON},
{"QValue YAML", "application/yaml;q=0.5", api.ContentTypeYAML},
{"QValue Multiple", "application/json;q=0.8, application/yaml;q=0.5", api.ContentTypeJSON},
{"Nimbus example", "application/octet-stream,application/json;q=0.9", api.ContentTypeSSZ},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req, err := http.NewRequest("GET", "http://example.com", http.NoBody)
assert.NoError(t, err)
req.Header.Set("Accept", tt.accept)

result := api.NewContentTypeFromRequest(req)
assert.Equal(t, tt.expected, result)
})
}
}
2 changes: 1 addition & 1 deletion pkg/api/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func NewBadRequestResponse(resolvers ContentTypeResolvers) *HTTPResponse {
func NewUnsupportedMediaTypeResponse(resolvers ContentTypeResolvers) *HTTPResponse {
return &HTTPResponse{
resolvers: resolvers,
StatusCode: http.StatusUnsupportedMediaType,
StatusCode: http.StatusNotAcceptable,
Headers: make(map[string]string),
ExtraData: make(map[string]interface{}),
}
Expand Down

0 comments on commit a2951b0

Please sign in to comment.