🚚 Moved ResponseType into separated response package
This commit is contained in:
parent
70d6163099
commit
392f98c925
|
@ -5,6 +5,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain/grant"
|
"source.toby3d.me/toby3d/auth/internal/domain/grant"
|
||||||
|
"source.toby3d.me/toby3d/auth/internal/domain/response"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Metadata struct {
|
type Metadata struct {
|
||||||
|
@ -55,7 +56,7 @@ type Metadata struct {
|
||||||
// JSON array containing the response_type values supported. This
|
// JSON array containing the response_type values supported. This
|
||||||
// differs from RFC8414 in that this parameter is OPTIONAL and that, if
|
// differs from RFC8414 in that this parameter is OPTIONAL and that, if
|
||||||
// omitted, the default is code.
|
// omitted, the default is code.
|
||||||
ResponseTypesSupported []ResponseType
|
ResponseTypesSupported []response.Type
|
||||||
|
|
||||||
// JSON array containing grant type values supported. If omitted, the
|
// JSON array containing grant type values supported. If omitted, the
|
||||||
// default value differs from RFC8414 and is authorization_code.
|
// default value differs from RFC8414 and is authorization_code.
|
||||||
|
@ -110,9 +111,9 @@ func TestMetadata(tb testing.TB) *Metadata {
|
||||||
ScopeRead,
|
ScopeRead,
|
||||||
ScopeUpdate,
|
ScopeUpdate,
|
||||||
},
|
},
|
||||||
ResponseTypesSupported: []ResponseType{
|
ResponseTypesSupported: []response.Type{
|
||||||
ResponseTypeCode,
|
response.Code,
|
||||||
ResponseTypeID,
|
response.ID,
|
||||||
},
|
},
|
||||||
GrantTypesSupported: []grant.Type{
|
GrantTypesSupported: []grant.Type{
|
||||||
grant.AuthorizationCode,
|
grant.AuthorizationCode,
|
||||||
|
|
96
internal/domain/response/type.go
Normal file
96
internal/domain/response/type.go
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package response
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"source.toby3d.me/toby3d/auth/internal/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
|
||||||
|
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
|
||||||
|
type Type struct {
|
||||||
|
responseType string
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:gochecknoglobals // structs cannot be constants
|
||||||
|
var (
|
||||||
|
Und = Type{} // "und"
|
||||||
|
|
||||||
|
// ID indicates to the authorization server that this is an
|
||||||
|
// authentication request. If this parameter is missing, the
|
||||||
|
// authorization endpoint MUST default to id.
|
||||||
|
//
|
||||||
|
// Deprecated: Only accept response_type=code requests, and for
|
||||||
|
// backwards-compatible support, treat response_type=id requests as
|
||||||
|
// response_type=code requests:
|
||||||
|
// https://aaronparecki.com/2020/12/03/1/indieauth-2020#response-type
|
||||||
|
ID = Type{"id"} // "id"
|
||||||
|
|
||||||
|
// Code indicates to the authorization server that an
|
||||||
|
// authorization code should be returned as the response:
|
||||||
|
// https://indieauth.net/source/#authorization-request-li-1
|
||||||
|
Code = Type{"code"} // "code"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrResponseTypeUnknown error = errors.New("unknown grant type")
|
||||||
|
|
||||||
|
// ParseType parse string as response type struct enum.
|
||||||
|
func ParseType(uid string) (Type, error) {
|
||||||
|
switch strings.ToLower(uid) {
|
||||||
|
case Code.responseType:
|
||||||
|
return Code, nil
|
||||||
|
case ID.responseType:
|
||||||
|
return ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return Und, fmt.Errorf("%w: %s", ErrResponseTypeUnknown, uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalForm implements custom unmarshler for form values.
|
||||||
|
func (t *Type) UnmarshalForm(src []byte) error {
|
||||||
|
responseType, err := ParseType(string(src))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ResponseType: UnmarshalForm: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*t = responseType
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements custom unmarshler for JSON.
|
||||||
|
func (t *Type) UnmarshalJSON(v []byte) error {
|
||||||
|
unquoted, err := strconv.Unquote(string(v))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ResponseType: UnmarshalJSON: cannot unquote value '%s': %w", string(v), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
responseType, err := ParseType(strings.ToLower(unquoted))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ResponseType: UnmarshalJSON: cannot parse value '%s': %w", unquoted, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*t = responseType
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Type) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(strconv.Quote(t.responseType)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns string representation of response type.
|
||||||
|
func (t Type) String() string {
|
||||||
|
if t == Und {
|
||||||
|
return common.Und
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.responseType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Type) GoString() string {
|
||||||
|
return "response.Type(" + t.String() + ")"
|
||||||
|
}
|
82
internal/domain/response/type_test.go
Normal file
82
internal/domain/response/type_test.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package response_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"source.toby3d.me/toby3d/auth/internal/domain/response"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseType(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for name, tc := range map[string]struct {
|
||||||
|
input string
|
||||||
|
expect response.Type
|
||||||
|
}{
|
||||||
|
"id": {input: "id", expect: response.ID},
|
||||||
|
"code": {input: "code", expect: response.Code},
|
||||||
|
} {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
actual, err := response.ParseType(tc.input)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actual != tc.expect {
|
||||||
|
t.Errorf("ParseResponseType(%s) = %v, want %v", tc.input, actual, tc.expect)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestType_UnmarshalForm(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
input := []byte("code")
|
||||||
|
actual := response.Und
|
||||||
|
|
||||||
|
if err := actual.UnmarshalForm(input); err != nil {
|
||||||
|
t.Fatalf("%+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actual != response.Code {
|
||||||
|
t.Errorf("UnmarshalForm(%s) = %v, want %v", input, actual, response.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestType_UnmarshalJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
input := []byte(`"code"`)
|
||||||
|
actual := response.Und
|
||||||
|
|
||||||
|
if err := actual.UnmarshalJSON(input); err != nil {
|
||||||
|
t.Fatalf("%+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actual != response.Code {
|
||||||
|
t.Errorf("UnmarshalJSON(%s) = %v, want %v", input, actual, response.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestType_String(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for name, tc := range map[string]struct {
|
||||||
|
input response.Type
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
"id": {input: response.ID, expect: "id"},
|
||||||
|
"code": {input: response.Code, expect: "code"},
|
||||||
|
} {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if actual := tc.input.String(); actual != tc.expect {
|
||||||
|
t.Errorf("String() = %s, want %s", actual, tc.expect)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,99 +0,0 @@
|
||||||
package domain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/auth/internal/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
|
|
||||||
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
|
|
||||||
type ResponseType struct {
|
|
||||||
responseType string
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:gochecknoglobals // structs cannot be constants
|
|
||||||
var (
|
|
||||||
ResponseTypeUnd = ResponseType{responseType: ""} // "und"
|
|
||||||
|
|
||||||
// ResponseTypeID indicates to the authorization server that this is an
|
|
||||||
// authentication request. If this parameter is missing, the
|
|
||||||
// authorization endpoint MUST default to id.
|
|
||||||
//
|
|
||||||
// Deprecated: Only accept response_type=code requests, and for
|
|
||||||
// backwards-compatible support, treat response_type=id requests as
|
|
||||||
// response_type=code requests:
|
|
||||||
// https://aaronparecki.com/2020/12/03/1/indieauth-2020#response-type
|
|
||||||
ResponseTypeID = ResponseType{responseType: "id"} // "id"
|
|
||||||
|
|
||||||
// ResponseTypeCode indicates to the authorization server that an
|
|
||||||
// authorization code should be returned as the response:
|
|
||||||
// https://indieauth.net/source/#authorization-request-li-1
|
|
||||||
ResponseTypeCode = ResponseType{responseType: "code"} // "code"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ErrResponseTypeUnknown error = NewError(
|
|
||||||
ErrorCodeUnsupportedResponseType,
|
|
||||||
"unknown grant type",
|
|
||||||
"https://indieauth.net/source/#authorization-request",
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseResponseType parse string as response type struct enum.
|
|
||||||
func ParseResponseType(uid string) (ResponseType, error) {
|
|
||||||
switch strings.ToLower(uid) {
|
|
||||||
case ResponseTypeCode.responseType:
|
|
||||||
return ResponseTypeCode, nil
|
|
||||||
case ResponseTypeID.responseType:
|
|
||||||
return ResponseTypeID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResponseTypeUnd, fmt.Errorf("%w: %s", ErrResponseTypeUnknown, uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalForm implements custom unmarshler for form values.
|
|
||||||
func (rt *ResponseType) UnmarshalForm(src []byte) error {
|
|
||||||
responseType, err := ParseResponseType(string(src))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("ResponseType: UnmarshalForm: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
*rt = responseType
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implements custom unmarshler for JSON.
|
|
||||||
func (rt *ResponseType) UnmarshalJSON(v []byte) error {
|
|
||||||
uid, err := strconv.Unquote(string(v))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("ResponseType: UnmarshalJSON: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
responseType, err := ParseResponseType(uid)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("ResponseType: UnmarshalJSON: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
*rt = responseType
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rt ResponseType) MarshalJSON() ([]byte, error) {
|
|
||||||
return []byte(strconv.Quote(rt.responseType)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns string representation of response type.
|
|
||||||
func (rt ResponseType) String() string {
|
|
||||||
if rt.responseType != "" {
|
|
||||||
return rt.responseType
|
|
||||||
}
|
|
||||||
|
|
||||||
return common.Und
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rt ResponseType) GoString() string {
|
|
||||||
return "domain.ResponseType(" + rt.String() + ")"
|
|
||||||
}
|
|
|
@ -1,89 +0,0 @@
|
||||||
//nolint:dupl
|
|
||||||
package domain_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestResponseTypeType(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for _, tc := range []struct {
|
|
||||||
in string
|
|
||||||
out domain.ResponseType
|
|
||||||
}{
|
|
||||||
{in: "id", out: domain.ResponseTypeID},
|
|
||||||
{in: "code", out: domain.ResponseTypeCode},
|
|
||||||
} {
|
|
||||||
tc := tc
|
|
||||||
|
|
||||||
t.Run(tc.in, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
result, err := domain.ParseResponseType(tc.in)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result != tc.out {
|
|
||||||
t.Errorf("ParseResponseType(%s) = %v, want %v", tc.in, result, tc.out)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResponseType_UnmarshalForm(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
input := []byte("code")
|
|
||||||
result := domain.ResponseTypeUnd
|
|
||||||
|
|
||||||
if err := result.UnmarshalForm(input); err != nil {
|
|
||||||
t.Fatalf("%+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result != domain.ResponseTypeCode {
|
|
||||||
t.Errorf("UnmarshalForm(%s) = %v, want %v", input, result, domain.ResponseTypeCode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResponseType_UnmarshalJSON(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
input := []byte(`"code"`)
|
|
||||||
result := domain.ResponseTypeUnd
|
|
||||||
|
|
||||||
if err := result.UnmarshalJSON(input); err != nil {
|
|
||||||
t.Fatalf("%+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result != domain.ResponseTypeCode {
|
|
||||||
t.Errorf("UnmarshalJSON(%s) = %v, want %v", input, result, domain.ResponseTypeCode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResponseType_String(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for _, tc := range []struct {
|
|
||||||
name string
|
|
||||||
in domain.ResponseType
|
|
||||||
out string
|
|
||||||
}{
|
|
||||||
{name: "id", in: domain.ResponseTypeID, out: "id"},
|
|
||||||
{name: "code", in: domain.ResponseTypeCode, out: "code"},
|
|
||||||
} {
|
|
||||||
tc := tc
|
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
result := tc.in.String()
|
|
||||||
if result != tc.out {
|
|
||||||
t.Errorf("String() = %v, want %v", result, tc.out)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user