Files
ziti/controller/api_impl/helpers.go
T
Paul Lorenz b6a2d2e951 Route library slog output through pfxlog. Fixes #4401
- adds common/slogbridge, a slog.Handler that emits records as logrus entries at the matching
  level, with attrs as fields, groups flattened to dotted keys, and the record caller set on the
  entry so pfxlog attributes each line to the library function that logged it
- gates on the logrus level at call time, so the configured level and agent set-log-level apply
  to library output without mirroring
- installs the handler as the root of foundation's logging registry and as the log/slog default,
  with the registry opened to trace so logrus is the only filter, in the CLI root init and both
  test package inits
- emits fatal and panic records at their own severity; Entry.Log never exits or panics
- moves foundation from the v2.0.86 pin to the commit carrying its logging registry, pending the
  v2.0.102 tag, and drops the replace directives in the root module and zititest
- renames errorz.ApiError.Code to AppCode at every site that range requires, in
  controller/apierror, api_impl, command, internal/routes, model, oidc_auth, raft and response
2026-09-15 13:27:13 -04:00

147 lines
4.7 KiB
Go

package api_impl
import (
"fmt"
openApiErrors "github.com/go-openapi/errors"
"github.com/michaelquigley/pfxlog"
"github.com/openziti/foundation/v2/errorz"
"github.com/openziti/ziti/controller/api"
apierror2 "github.com/openziti/ziti/controller/apierror"
"github.com/openziti/ziti/controller/rest_model"
"net/http"
)
// Wrapper for the OpenAPI REST server to allow the the Edge API Error message responses to be used
func ServeError(rw http.ResponseWriter, r *http.Request, inErr error) {
if openApiError, ok := inErr.(openApiErrors.Error); ok {
//openApiErrors from the Open API framework mean that we never hit any of the Edge logic and thus
//do not have any context established (i.e. no request id)
var apiError *errorz.ApiError
if openApiError.Code() == http.StatusUnprocessableEntity {
// triggered by validation failures and consumer errors
var newApiError *errorz.ApiError
if compositeError, ok := openApiError.(*openApiErrors.CompositeError); ok {
if len(compositeError.Errors) > 0 {
//validation errors
if validationError, ok := compositeError.Errors[0].(*openApiErrors.Validation); ok {
newApiError = errorz.NewCouldNotValidate(validationError)
}
}
}
// only other option is could not parse
if newApiError == nil {
newApiError = apierror2.NewCouldNotParseBody(openApiError)
}
apiError = newApiError
} else if openApiError.Code() == http.StatusNotFound {
// handle open API openApiErrors we have existing ApiErrors for
apiError = errorz.NewNotFound()
} else if openApiError.Code() == http.StatusMethodNotAllowed {
apiError = apierror2.NewMethodNotAllowed()
} else if openApiError.Code() == http.StatusUnauthorized {
apiError = errorz.NewUnauthorized()
} else if openApiError.Code() >= 600 && openApiError.Code() < 700 {
//openapi defines error codes 601+ for validation errors
apiError = errorz.NewCouldNotValidate(inErr)
} else {
apiError = errorz.NewUnhandled(openApiError)
}
apiError.Cause = openApiError
NewRequestContext(rw, r).RespondWithApiError(apiError)
return
}
requestContext, err := api.GetRequestContextFromHttpContext(r)
if requestContext == nil || err != nil {
pfxlog.Logger().WithError(err).Error("failed to retrieve request context")
requestContext = NewRequestContext(rw, r)
}
requestContext.RespondWithError(inErr)
}
func ToRestModel(e *errorz.ApiError, requestId string) *rest_model.APIError {
ret := &rest_model.APIError{
Args: nil,
Code: e.AppCode,
Message: e.Message,
RequestID: requestId,
}
if e.Cause != nil {
//unwrap first error in composite error
compositeErr, ok := e.Cause.(*openApiErrors.CompositeError)
for ok {
e.Cause = compositeErr.Errors[0]
compositeErr, ok = e.Cause.(*openApiErrors.CompositeError)
}
if causeApiError, ok := e.Cause.(*errorz.ApiError); ok {
//standard apierror
ret.Cause = &rest_model.APIErrorCause{
APIError: *ToRestModel(causeApiError, requestId),
}
} else if causeJsonSchemaError, ok := e.Cause.(*apierror2.ValidationErrors); ok {
//only possible from config type JSON schema validation
ret.Cause = &rest_model.APIErrorCause{
APIFieldError: rest_model.APIFieldError{
Field: causeJsonSchemaError.Errors[0].Field,
Reason: causeJsonSchemaError.Errors[0].Error(),
Value: fmt.Sprintf("%v", causeJsonSchemaError.Errors[0].Value),
},
}
} else if causeFieldErr, ok := e.Cause.(*errorz.FieldError); ok {
//authenticator modules and enrollment only
//todo: see if we can remove this by not using FieldError
ret.Cause = &rest_model.APIErrorCause{
APIFieldError: rest_model.APIFieldError{
Field: causeFieldErr.FieldName,
Value: fmt.Sprintf("%v", causeFieldErr.FieldValue),
Reason: causeFieldErr.Reason,
},
}
if ret.Code == errorz.InvalidFieldCode {
ret.Code = errorz.CouldNotValidateCode
ret.Message = errorz.CouldNotValidateMessage
}
} else if causeFieldErr, ok := e.Cause.(*openApiErrors.Validation); ok {
//open api validation errors
ret.Cause = &rest_model.APIErrorCause{
APIFieldError: rest_model.APIFieldError{
Field: causeFieldErr.Name,
Reason: causeFieldErr.Error(),
Value: fmt.Sprintf("%v", causeFieldErr.Value),
},
}
ret.Code = errorz.CouldNotValidateCode
ret.Message = errorz.CouldNotValidateMessage
} else if genericErr, ok := e.Cause.(*apierror2.GenericCauseError); ok {
ret.Cause = &rest_model.APIErrorCause{
APIError: rest_model.APIError{
Data: genericErr.DataMap,
Message: genericErr.Error(),
},
}
} else {
ret.Cause = &rest_model.APIErrorCause{
APIError: rest_model.APIError{
Code: errorz.UnhandledCode,
Message: e.Cause.Error(),
},
}
}
}
return ret
}