Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ COPY . ./
# Regenerate the swagger
RUN make swag

# Build it
RUN GOOS=linux go build -o /api
# Build it: a static binary, since the production image has no libc.
RUN CGO_ENABLED=0 GOOS=linux go build -o /api

FROM gcr.io/distroless/base-debian12@sha256:fabbf1c0c357a3d42550111351daed089b20a2c954df13ee2fcff60602515e84 AS production
FROM gcr.io/distroless/static-debian13@sha256:15f8637509792e05b97d83451b0b6ccfa4abd548f997e406ac1e72c87fe1fa78 AS production

# The release and preview workflows pass VERSION (X.Y.Z, X.Y.Z-rcN, main-<sha7> or
# pr-<number>-<sha7>) and set the source and revision labels themselves.
Expand Down
9 changes: 3 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ require (
github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.11.0
github.com/joho/godotenv v1.5.1
github.com/labstack/echo-contrib v0.50.1
github.com/labstack/echo/v4 v4.16.1
github.com/labstack/echo-contrib/v5 v5.0.1
github.com/labstack/echo/v5 v5.4.2
github.com/moby/moby/api v1.56.1
github.com/open-policy-agent/opa v1.21.3
github.com/prometheus/client_golang v1.25.0
Expand All @@ -35,7 +35,7 @@ require (
github.com/spf13/cobra v1.10.2
github.com/spf13/viper v1.21.0
github.com/stretchr/testify v1.12.1
github.com/swaggo/echo-swagger v1.5.2
github.com/swaggo/echo-swagger/v2 v2.0.1
github.com/swaggo/swag v1.16.6
github.com/testcontainers/testcontainers-go v0.44.0
github.com/testcontainers/testcontainers-go/modules/postgres v0.44.0
Expand Down Expand Up @@ -122,7 +122,6 @@ require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/klauspost/compress v1.20.1 // indirect
github.com/labstack/gommon v0.5.0 // indirect
github.com/leodido/go-urn v1.5.0 // indirect
github.com/lestrrat-go/blackmagic v1.0.4 // indirect
github.com/lestrrat-go/dsig v1.4.0 // indirect
Expand Down Expand Up @@ -184,9 +183,7 @@ require (
github.com/tklauser/go-sysconf v0.4.0 // indirect
github.com/tklauser/numcpus v0.12.0 // indirect
github.com/urfave/cli/v2 v2.27.7 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fastjson v1.6.10 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/vektah/gqlparser/v2 v2.5.37 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
Expand Down
18 changes: 6 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -325,12 +325,10 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/kyokomi/emoji/v2 v2.2.13 h1:GhTfQa67venUUvmleTNFnb+bi7S3aocF7ZCXU9fSO7U=
github.com/kyokomi/emoji/v2 v2.2.13/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE=
github.com/labstack/echo-contrib v0.50.1 h1:W9cZZ9viA4TDdFtm8cuA+XGFwOcnfbjJpl7VgfsRLHE=
github.com/labstack/echo-contrib v0.50.1/go.mod h1:8r/++U/Fw/QniApFnzunLanKaviPfBX7fX7/2QX0qOk=
github.com/labstack/echo/v4 v4.16.1 h1:h05NuF1GvNu6jSAZxOv++AdjD/NJK+mZdGy0thiiuns=
github.com/labstack/echo/v4 v4.16.1/go.mod h1:V/EQaTVHYJH92QhbQe7kFQ4mjgcjxLQQUQtAN87zZdw=
github.com/labstack/gommon v0.5.0 h1:6VSQ2NOzsnEJ5W6+84E0RbcaDDmgB6NIAzWCczTEe6c=
github.com/labstack/gommon v0.5.0/go.mod h1:Rzlg7HHy1maLfzBYGg9NZcVuz1sA68HHhLjhcEllYE0=
github.com/labstack/echo-contrib/v5 v5.0.1 h1:Z23m8p1F9Doax+pa8ek8ll5d6wvzCbvrAn25ZWmSgrE=
github.com/labstack/echo-contrib/v5 v5.0.1/go.mod h1:ajrqEwSloW0vQSTfJ4j9u4tHoS+dg4umCDPxuTvChZs=
github.com/labstack/echo/v5 v5.4.2 h1:6Jk4ur2Sc7R6vH3DflrvfWmyrf9xX0yosFKBn+/8nZA=
github.com/labstack/echo/v5 v5.4.2/go.mod h1:4iEGNQiPPZnkfYpNR/L6fINd3NLiGWUD5+eBotFALas=
github.com/leodido/go-urn v1.5.0 h1:pLqT2kq1zpHW/1D18QMjMpdtX7cekxqtJJjg5ANyWw0=
github.com/leodido/go-urn v1.5.0/go.mod h1:9BORnCDhdPBJNDEX+w1bJisa8yOKYi116VeO96s4ifE=
github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
Expand Down Expand Up @@ -503,8 +501,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/sv-tools/openapi v0.2.1 h1:ES1tMQMJFGibWndMagvdoo34T1Vllxr1Nlm5wz6b1aA=
github.com/sv-tools/openapi v0.2.1/go.mod h1:k5VuZamTw1HuiS9p2Wl5YIDWzYnHG6/FgPOSFXLAhGg=
github.com/swaggo/echo-swagger v1.5.2 h1:KUM4QuEO1r/maky6Ybb9wS5MFEkJUpXwPbK4wwBe5Uk=
github.com/swaggo/echo-swagger v1.5.2/go.mod h1:nt3Z+SlyzXNIQ4odFNlPzRdcNOFvkPJHf+t4sMLhNu4=
github.com/swaggo/echo-swagger/v2 v2.0.1 h1:jKR3QiK+ciGjxE0+7qZ/azjtlx/pTVls7pJFJqdJoJI=
github.com/swaggo/echo-swagger/v2 v2.0.1/go.mod h1:BbgiO9XKX6yYU5Rq4ejqVlQI0mVRv6ziFKd0XgdztnQ=
github.com/swaggo/files/v2 v2.0.2 h1:Bq4tgS/yxLB/3nwOMcul5oLEUKa877Ykgz3CJMVbQKU=
github.com/swaggo/files/v2 v2.0.2/go.mod h1:TVqetIzZsO9OhHX1Am9sRf9LdrFZqoK49N37KON/jr0=
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
Expand Down Expand Up @@ -543,12 +541,8 @@ github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqo
github.com/tklauser/numcpus v0.12.0/go.mod h1:ABHeXzJnr/qqwguhClkZKT1/8VABcYrsyUiUGobwWJg=
github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU=
github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4=
github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vektah/gqlparser/v2 v2.5.37 h1:jbb1Ilv+xBklV6653tKb4oVUupPNTLb5LmrnBKVI12Y=
github.com/vektah/gqlparser/v2 v2.5.37/go.mod h1:9O4Ox6Ngd3Y12bMD3w6i3CRQXh8W1oC1q0m6olCymDM=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
Expand Down
4 changes: 2 additions & 2 deletions internal/api/authcontext/signer_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import (
"github.com/compliance-framework/api/internal/api/middleware"
"github.com/compliance-framework/api/internal/authn"
evidencesvc "github.com/compliance-framework/api/internal/service/relational/evidence"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v5"
)

func SignerContextFromEcho(ctx echo.Context) *evidencesvc.SignerContext {
func SignerContextFromEcho(ctx *echo.Context) *evidencesvc.SignerContext {
if claims, ok := ctx.Get("user").(*authn.UserClaims); ok && claims != nil {
return evidencesvc.NewUserSignerContextFromClaims(claims)
}
Expand Down
8 changes: 4 additions & 4 deletions internal/api/binders/custom_binder.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"io"
"net/http"

"github.com/labstack/echo/v4"
"github.com/labstack/echo/v5"
"gopkg.in/yaml.v2"
)

Expand All @@ -15,17 +15,17 @@ const (

type CustomBinder struct{}

func (cb *CustomBinder) Bind(i any, c echo.Context) error {
func (cb *CustomBinder) Bind(c *echo.Context, i any) error {
req := c.Request()
contentType := req.Header.Get(echo.HeaderContentType)
switch contentType {
case MIMEApplicationYAML:
if err := yaml.NewDecoder(req.Body).Decode(i); err != nil && err != io.EOF {
return echo.NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
return echo.NewHTTPError(http.StatusBadRequest, err.Error()).Wrap(err)
}
case MIMEApplicationJSON:
defaultBinder := new(echo.DefaultBinder)
if err := defaultBinder.Bind(i, c); err != nil {
if err := defaultBinder.Bind(c, i); err != nil {
return err
}
default:
Expand Down
7 changes: 6 additions & 1 deletion internal/api/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (

"github.com/go-playground/validator/v10"

"github.com/labstack/echo/v4"
"github.com/labstack/echo/v5"
)

type Error struct {
Expand All @@ -21,6 +21,11 @@ func NewError(err error) Error {
switch {
case errors.As(err, &v):
e.Errors["body"] = v.Message
// Echo's binders return a generic 400 wrapping the decode error; that error says
// what was wrong with the request, as echo v4's message did.
if wrapped := v.Unwrap(); wrapped != nil {
e.Errors["body"] = wrapped.Error()
}
default:
e.Errors["body"] = err.Error()
}
Expand Down
32 changes: 32 additions & 0 deletions internal/api/error_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package api

import (
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/labstack/echo/v5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestNewErrorReportsWhyABindFailed(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{"name": 1}`))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
ctx := echo.New().NewContext(req, httptest.NewRecorder())

var body struct {
Name string `json:"name"`
}
err := ctx.Bind(&body)
require.Error(t, err)

assert.Contains(t, NewError(err).Errors["body"], "cannot unmarshal number")
}

func TestNewErrorUsesTheMessageOfAnUnwrappedHTTPError(t *testing.T) {
assert.Equal(t, "workflow_instance_id is required", NewError(echo.NewHTTPError(http.StatusBadRequest, "workflow_instance_id is required")).Errors["body"])
assert.Equal(t, "plain", NewError(errors.New("plain")).Errors["body"])
}
34 changes: 17 additions & 17 deletions internal/api/handler/agent_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
"github.com/compliance-framework/api/internal/service/relational/agentcfg"
"github.com/compliance-framework/api/pkg/agentconfig"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v5"
"go.uber.org/zap"
"gorm.io/gorm"
)
Expand Down Expand Up @@ -204,7 +204,7 @@ type agentConfigPreviewRequest struct {
// @Failure 500 {object} api.Error
// @Security OAuth2Password
// @Router /admin/agents/{id}/config [get]
func (h *AgentConfigHandler) Get(ctx echo.Context) error {
func (h *AgentConfigHandler) Get(ctx *echo.Context) error {
agent, errResp := h.resolveAgent(ctx)
if agent == nil {
return errResp
Expand Down Expand Up @@ -244,7 +244,7 @@ func (h *AgentConfigHandler) Get(ctx echo.Context) error {
// @Failure 500 {object} api.Error
// @Security OAuth2Password
// @Router /admin/agents/{id}/config [put]
func (h *AgentConfigHandler) Put(ctx echo.Context) error {
func (h *AgentConfigHandler) Put(ctx *echo.Context) error {
agent, errResp := h.resolveAgent(ctx)
if agent == nil {
return errResp
Expand Down Expand Up @@ -295,7 +295,7 @@ func (h *AgentConfigHandler) Put(ctx echo.Context) error {
// @Failure 500 {object} api.Error
// @Security OAuth2Password
// @Router /admin/agents/{id}/config/revisions/{rev}/revert [post]
func (h *AgentConfigHandler) Revert(ctx echo.Context) error {
func (h *AgentConfigHandler) Revert(ctx *echo.Context) error {
agent, errResp := h.resolveAgent(ctx)
if agent == nil {
return errResp
Expand Down Expand Up @@ -333,7 +333,7 @@ func (h *AgentConfigHandler) Revert(ctx echo.Context) error {
}

// save implements PUT/revert steps 3-7 (A4.3).
func (h *AgentConfigHandler) save(ctx echo.Context, agent *relational.Agent, expected int64, overlay json.RawMessage, comment *string, revertOf *int64) error {
func (h *AgentConfigHandler) save(ctx *echo.Context, agent *relational.Agent, expected int64, overlay json.RawMessage, comment *string, revertOf *int64) error {
reqCtx := ctx.Request().Context()
agentID := *agent.ID

Expand Down Expand Up @@ -413,7 +413,7 @@ func (h *AgentConfigHandler) save(ctx echo.Context, agent *relational.Agent, exp
// @Failure 500 {object} api.Error
// @Security OAuth2Password
// @Router /admin/agents/{id}/config/preview [post]
func (h *AgentConfigHandler) Preview(ctx echo.Context) error {
func (h *AgentConfigHandler) Preview(ctx *echo.Context) error {
agent, errResp := h.resolveAgent(ctx)
if agent == nil {
return errResp
Expand Down Expand Up @@ -625,7 +625,7 @@ func mergeAndValidate(base agentconfig.Config, overlay json.RawMessage) (*agentc
// @Failure 500 {object} api.Error
// @Security OAuth2Password
// @Router /admin/agents/{id}/config/revisions [get]
func (h *AgentConfigHandler) ListRevisions(ctx echo.Context) error {
func (h *AgentConfigHandler) ListRevisions(ctx *echo.Context) error {
agent, errResp := h.resolveAgent(ctx)
if agent == nil {
return errResp
Expand Down Expand Up @@ -670,7 +670,7 @@ func (h *AgentConfigHandler) ListRevisions(ctx echo.Context) error {
// @Failure 500 {object} api.Error
// @Security OAuth2Password
// @Router /admin/agents/{id}/config/revisions/{rev} [get]
func (h *AgentConfigHandler) GetRevision(ctx echo.Context) error {
func (h *AgentConfigHandler) GetRevision(ctx *echo.Context) error {
agent, errResp := h.resolveAgent(ctx)
if agent == nil {
return errResp
Expand Down Expand Up @@ -709,7 +709,7 @@ func (h *AgentConfigHandler) GetRevision(ctx echo.Context) error {
// @Failure 500 {object} api.Error
// @Security OAuth2Password
// @Router /admin/agents/{id}/instances [get]
func (h *AgentConfigHandler) ListInstances(ctx echo.Context) error {
func (h *AgentConfigHandler) ListInstances(ctx *echo.Context) error {
agent, errResp := h.resolveAgent(ctx)
if agent == nil {
return errResp
Expand Down Expand Up @@ -763,7 +763,7 @@ func (h *AgentConfigHandler) ListInstances(ctx echo.Context) error {
// @Failure 500 {object} api.Error
// @Security OAuth2Password
// @Router /admin/agents/{id}/instances/{instanceId} [get]
func (h *AgentConfigHandler) GetInstance(ctx echo.Context) error {
func (h *AgentConfigHandler) GetInstance(ctx *echo.Context) error {
agent, errResp := h.resolveAgent(ctx)
if agent == nil {
return errResp
Expand Down Expand Up @@ -841,7 +841,7 @@ func (h *AgentConfigHandler) decodeColumn(inst *relational.AgentInstance, column
}

// resolveAgent loads :id. On failure it returns nil and the already-written error response.
func (h *AgentConfigHandler) resolveAgent(ctx echo.Context) (*relational.Agent, error) {
func (h *AgentConfigHandler) resolveAgent(ctx *echo.Context) (*relational.Agent, error) {
id, err := uuid.Parse(ctx.Param("id"))
if err != nil {
return nil, ctx.JSON(http.StatusBadRequest, api.InvalidUUID())
Expand All @@ -859,7 +859,7 @@ func (h *AgentConfigHandler) resolveAgent(ctx echo.Context) (*relational.Agent,
// canConfigure reports whether the caller holds agent:configure on the agent in :id,
// evaluated against the PDP like the route guard does. It fails closed: no PDP, an
// unavailable PDP or an evaluation error all count as no.
func (h *AgentConfigHandler) canConfigure(ctx echo.Context) bool {
func (h *AgentConfigHandler) canConfigure(ctx *echo.Context) bool {
if h.pdp == nil {
return false
}
Expand All @@ -877,7 +877,7 @@ func (h *AgentConfigHandler) canConfigure(ctx echo.Context) bool {
// redactForReader redacts resp.Overlay (agentconfig.RedactDocument) unless the caller holds
// agent:configure, so plain readers never see literal secrets typed into an overlay.
// OverlaySize stays the stored size.
func (h *AgentConfigHandler) redactForReader(ctx echo.Context, resp *agentConfigRevisionResponse) error {
func (h *AgentConfigHandler) redactForReader(ctx *echo.Context, resp *agentConfigRevisionResponse) error {
if len(resp.Overlay) == 0 || h.canConfigure(ctx) {
return nil
}
Expand All @@ -889,7 +889,7 @@ func (h *AgentConfigHandler) redactForReader(ctx echo.Context, resp *agentConfig
return nil
}

func (h *AgentConfigHandler) internalError(ctx echo.Context, what string, err error) error {
func (h *AgentConfigHandler) internalError(ctx *echo.Context, what string, err error) error {
h.sugar.Errorw("Agent configuration request failed", "operation", what, "path", ctx.Path(), "error", err)
return ctx.JSON(http.StatusInternalServerError, api.InternalServerError())
}
Expand Down Expand Up @@ -919,19 +919,19 @@ func revisionResponse(agentID uuid.UUID, rev *relational.AgentConfigRevision, wi
return resp
}

func preconditionRequired(ctx echo.Context) error {
func preconditionRequired(ctx *echo.Context) error {
return ctx.JSON(http.StatusPreconditionRequired, api.NewError(errors.New("If-Match header with the current revision is required")))
}

func revisionConflict(ctx echo.Context, current int64) error {
func revisionConflict(ctx *echo.Context, current int64) error {
return ctx.JSON(http.StatusConflict, api.Error{Errors: map[string]any{
"body": agentcfg.ErrRevisionConflict.Error(),
"current-revision": current,
}})
}

// revisionAuthor returns the user subject (email) and user_uuid claim of the caller.
func revisionAuthor(ctx echo.Context) (string, *uuid.UUID) {
func revisionAuthor(ctx *echo.Context) (string, *uuid.UUID) {
claims, ok := ctx.Get("user").(*authn.UserClaims)
if !ok || claims == nil {
return "", nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
"github.com/compliance-framework/api/internal/tests"
"github.com/compliance-framework/api/pkg/agentconfig"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v5"
"github.com/stretchr/testify/suite"
"go.uber.org/zap"
)
Expand Down
6 changes: 3 additions & 3 deletions internal/api/handler/agent_config_body.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"strings"

"github.com/compliance-framework/api/internal/api"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v5"
)

// HTTP header names used by the agent-configuration routes.
Expand All @@ -28,15 +28,15 @@ type bodyError struct {
func (e *bodyError) Error() string { return e.msg }

// respond writes the error as an api.Error body.
func (e *bodyError) respond(ctx echo.Context) error {
func (e *bodyError) respond(ctx *echo.Context) error {
return ctx.JSON(e.status, api.NewError(errors.New(e.msg)))
}

// readJSONBody reads a JSON request body for the agent-configuration handlers (R13). They do
// not use ctx.Bind, because CustomBinder rejects "application/json; charset=utf-8". A missing
// Content-Type or any application/json media type (with parameters) is accepted; any other
// type is 415. More than limit bytes is 413. An empty body returns nil.
func readJSONBody(ctx echo.Context, limit int64) ([]byte, *bodyError) {
func readJSONBody(ctx *echo.Context, limit int64) ([]byte, *bodyError) {
if ct := strings.TrimSpace(ctx.Request().Header.Get(echo.HeaderContentType)); ct != "" {
mediaType, _, err := mime.ParseMediaType(ct)
if err != nil || !strings.EqualFold(mediaType, echo.MIMEApplicationJSON) {
Expand Down
Loading
Loading