diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1737807..ada1fa9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -29,7 +29,7 @@ jobs: golangci-lint: runs-on: ubuntu-latest env: - GOLANGCI_LINT_VERSION: v2.0.0 + GOLANGCI_LINT_VERSION: v2.4.0 steps: - name: Checkout diff --git a/.golangci.yaml b/.golangci.yaml index a5773f0..e5cef5d 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -25,6 +25,7 @@ linters: - ireturn # bans returning interfaces; questionable as is, but also buggy as hell; very, very annoying - lll # restricts maximum line length; annoying - nlreturn # requires a blank line before returns; annoying + - noinlineerr # disallows `if err := ...`; because why miss an opportunity to leak variables out of scope? - wsl # a bunch of style/whitespace stuff; annoying settings: diff --git a/apiendpoint/api_endpoint.go b/apiendpoint/api_endpoint.go index 148a94a..e069d27 100644 --- a/apiendpoint/api_endpoint.go +++ b/apiendpoint/api_endpoint.go @@ -85,6 +85,7 @@ func (m *EndpointMeta) validate() { if m.Pattern == "" { panic("Endpoint.Path is required") } + if m.StatusCode == 0 { panic("Endpoint.StatusCode is required") } @@ -142,6 +143,7 @@ func executeAPIEndpoint[TReq any, TResp any](w http.ResponseWriter, r *http.Requ err := func() error { var req TReq + if r.Method != http.MethodGet { reqData, err := io.ReadAll(r.Body) if err != nil { @@ -149,6 +151,7 @@ func executeAPIEndpoint[TReq any, TResp any](w http.ResponseWriter, r *http.Requ if errors.As(err, &maxBytesErr) { return apierror.NewRequestEntityTooLarge("Request entity too large.") } + return fmt.Errorf("error reading request body: %w", err) } @@ -213,12 +216,14 @@ func executeAPIEndpoint[TReq any, TResp any](w http.ResponseWriter, r *http.Requ logger.InfoContext(ctx, "API error response", logAttrs...) apiErr.Write(ctx, logger, w) + return } if errors.Is(err, context.DeadlineExceeded) { logger.ErrorContext(ctx, "request timeout", slog.String("error", err.Error())) apierror.NewServiceUnavailable("Request timed out. Retrying the request might work.").Write(ctx, logger, w) + return } diff --git a/apiendpoint/api_endpoint_test.go b/apiendpoint/api_endpoint_test.go index 5691f7d..dc0a778 100644 --- a/apiendpoint/api_endpoint_test.go +++ b/apiendpoint/api_endpoint_test.go @@ -245,6 +245,7 @@ func mustMarshalJSON(t *testing.T, v any) []byte { data, err := json.Marshal(v) require.NoError(t, err) + return data } @@ -252,8 +253,10 @@ func mustUnmarshalJSON[T any](t *testing.T, data []byte) *T { t.Helper() var val T + err := json.Unmarshal(data, &val) require.NoError(t, err) + return &val } @@ -315,6 +318,7 @@ func (a *getEndpoint) Execute(_ context.Context, req *getRequest) (*getResponse, type postEndpoint struct { Endpoint[postRequest, postResponse] + MaxBodyBytes int64 } @@ -341,6 +345,7 @@ func (req *postRequest) ExtractRaw(r *http.Request) error { } req.ID = r.PathValue("id") + return nil } diff --git a/apierror/api_error_test.go b/apierror/api_error_test.go index d2fa195..79150a9 100644 --- a/apierror/api_error_test.go +++ b/apierror/api_error_test.go @@ -70,5 +70,6 @@ func mustMarshalJSON(t *testing.T, v any) []byte { data, err := json.Marshal(v) require.NoError(t, err) + return data } diff --git a/apimiddleware/api_middleware.go b/apimiddleware/api_middleware.go index 9149d8f..fd70844 100644 --- a/apimiddleware/api_middleware.go +++ b/apimiddleware/api_middleware.go @@ -56,6 +56,7 @@ func NewMiddlewareStack(middlewares ...middlewareInterface) *MiddlewareStack { for _, mw := range middlewares { stack.Use(mw) } + return stack } @@ -63,6 +64,7 @@ func (s *MiddlewareStack) Mount(handler http.Handler) http.Handler { for i := len(s.middlewares) - 1; i >= 0; i-- { handler = s.middlewares[i].Middleware(handler) } + return handler } diff --git a/apimiddleware/api_middleware_test.go b/apimiddleware/api_middleware_test.go index a0c323e..adc48f8 100644 --- a/apimiddleware/api_middleware_test.go +++ b/apimiddleware/api_middleware_test.go @@ -34,6 +34,7 @@ func (m *contextTrailMiddleware) Middleware(next http.Handler) http.Handler { if existingTrail, ok := ctx.Value(contextTrailContextKey{}).([]string); ok { contextTrail = existingTrail } + contextTrail = append(contextTrail, m.segment) next.ServeHTTP(w, r.WithContext(context.WithValue(ctx, contextTrailContextKey{}, contextTrail))) diff --git a/apitest/apitest_test.go b/apitest/apitest_test.go index dd5a204..b81fd62 100644 --- a/apitest/apitest_test.go +++ b/apitest/apitest_test.go @@ -19,6 +19,7 @@ func TestInvokeHandler(t *testing.T) { type testRequest struct { RequiredReqField string `json:"req_field" validate:"required"` } + type testResponse struct { RequiredRespField string `json:"resp_field" validate:"required"` } diff --git a/apitype/explicit_nullable.go b/apitype/explicit_nullable.go index 074dee2..39f6425 100644 --- a/apitype/explicit_nullable.go +++ b/apitype/explicit_nullable.go @@ -42,5 +42,6 @@ func ExtractExplicitNullableValueForValidation[T any](field reflect.Value) inter if !ok || !ps.Set || ps.Value == nil { return nil } + return ps.Value } diff --git a/apitype/explicit_nullable_test.go b/apitype/explicit_nullable_test.go index bf2288f..1bd1985 100644 --- a/apitype/explicit_nullable_test.go +++ b/apitype/explicit_nullable_test.go @@ -56,6 +56,7 @@ func TestExplicitNullable_Validation(t *testing.T) { t.Parallel() var payload testPayload + err := json.Unmarshal([]byte(tt.json), &payload) require.NoError(t, err) @@ -104,6 +105,7 @@ func TestExtractExplicitNullableValueForValidation(t *testing.T) { t.Parallel() val := reflect.ValueOf(tt.input) + got := ExtractExplicitNullableValueForValidation[string](val) if tt.wantVal == nil { require.Nil(t, got) @@ -159,6 +161,7 @@ func TestExplicitNullable_UnmarshalJSON(t *testing.T) { t.Parallel() var got testPayload + err := json.Unmarshal([]byte(tt.json), &got) if tt.wantErr { diff --git a/go.mod b/go.mod index ef19d6d..c242bb2 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/riverqueue/apiframe -go 1.23.0 +go 1.24.0 toolchain go1.24.1 @@ -8,7 +8,7 @@ require ( github.com/go-playground/validator/v10 v10.27.0 github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 github.com/jackc/pgx/v5 v5.7.6 - github.com/riverqueue/river/rivershared v0.24.0 + github.com/riverqueue/river/rivershared v0.25.0 github.com/stretchr/testify v1.11.1 ) @@ -23,13 +23,13 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/riverqueue/river/rivertype v0.24.0 // indirect + github.com/riverqueue/river/rivertype v0.25.0 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect go.uber.org/goleak v1.3.0 // indirect golang.org/x/crypto v0.37.0 // indirect golang.org/x/net v0.38.0 // indirect - golang.org/x/sync v0.16.0 // indirect + golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.32.0 // indirect - golang.org/x/text v0.28.0 // indirect + golang.org/x/text v0.29.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 5d3f6af..7680123 100644 --- a/go.sum +++ b/go.sum @@ -30,10 +30,10 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/riverqueue/river/rivershared v0.24.0 h1:KysokksW75pug2a5RTOc6WESOupWmsylVc6VWvAx+4Y= -github.com/riverqueue/river/rivershared v0.24.0/go.mod h1:UIBfSdai0oWFlwFcoqG4DZX83iA/fLWTEBGrj7Oe1ho= -github.com/riverqueue/river/rivertype v0.24.0 h1:xrQZm/h6U8TBPyTsQPYD5leOapuoBAcdz30bdBwTqOg= -github.com/riverqueue/river/rivertype v0.24.0/go.mod h1:lmdl3vLNDfchDWbYdW2uAocIuwIN+ZaXqAukdSCFqWs= +github.com/riverqueue/river/rivershared v0.25.0 h1:grjuTHJEVvi4srzcspQ2UXWjISxdqbubQl+9DDg3agQ= +github.com/riverqueue/river/rivershared v0.25.0/go.mod h1:ZdVeOnT8X8PiAZRUfWHc+Ne6fNXqe1oYb2eioZb6URM= +github.com/riverqueue/river/rivertype v0.25.0 h1:DPwd0DGqajLIv9zsB+BOwlum0D1/4Iiqz34+nwIZaZ0= +github.com/riverqueue/river/rivertype v0.25.0/go.mod h1:9bbWVYkr1B/YzW43lUs/Vk/tEYqLrabrZWrtUWQ+Goo= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -47,12 +47,12 @@ golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=