From 392f98c925a3e9343126a93310ef180a2e9e3442 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Mon, 6 May 2024 18:24:25 +0500 Subject: [PATCH] :truck: Moved ResponseType into separated response package --- internal/domain/metadata.go | 9 +-- internal/domain/response/type.go | 96 ++++++++++++++++++++++++++ internal/domain/response/type_test.go | 82 ++++++++++++++++++++++ internal/domain/response_type.go | 99 --------------------------- internal/domain/response_type_test.go | 89 ------------------------ 5 files changed, 183 insertions(+), 192 deletions(-) create mode 100644 internal/domain/response/type.go create mode 100644 internal/domain/response/type_test.go delete mode 100644 internal/domain/response_type.go delete mode 100644 internal/domain/response_type_test.go diff --git a/internal/domain/metadata.go b/internal/domain/metadata.go index 2cc684e..3555fac 100644 --- a/internal/domain/metadata.go +++ b/internal/domain/metadata.go @@ -5,6 +5,7 @@ import ( "testing" "source.toby3d.me/toby3d/auth/internal/domain/grant" + "source.toby3d.me/toby3d/auth/internal/domain/response" ) type Metadata struct { @@ -55,7 +56,7 @@ type Metadata struct { // JSON array containing the response_type values supported. This // differs from RFC8414 in that this parameter is OPTIONAL and that, if // omitted, the default is code. - ResponseTypesSupported []ResponseType + ResponseTypesSupported []response.Type // JSON array containing grant type values supported. If omitted, the // default value differs from RFC8414 and is authorization_code. @@ -110,9 +111,9 @@ func TestMetadata(tb testing.TB) *Metadata { ScopeRead, ScopeUpdate, }, - ResponseTypesSupported: []ResponseType{ - ResponseTypeCode, - ResponseTypeID, + ResponseTypesSupported: []response.Type{ + response.Code, + response.ID, }, GrantTypesSupported: []grant.Type{ grant.AuthorizationCode, diff --git a/internal/domain/response/type.go b/internal/domain/response/type.go new file mode 100644 index 0000000..6cd74f1 --- /dev/null +++ b/internal/domain/response/type.go @@ -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() + ")" +} diff --git a/internal/domain/response/type_test.go b/internal/domain/response/type_test.go new file mode 100644 index 0000000..bae63d5 --- /dev/null +++ b/internal/domain/response/type_test.go @@ -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) + } + }) + } +} diff --git a/internal/domain/response_type.go b/internal/domain/response_type.go deleted file mode 100644 index 3c9cd87..0000000 --- a/internal/domain/response_type.go +++ /dev/null @@ -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() + ")" -} diff --git a/internal/domain/response_type_test.go b/internal/domain/response_type_test.go deleted file mode 100644 index 91a8619..0000000 --- a/internal/domain/response_type_test.go +++ /dev/null @@ -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) - } - }) - } -}