mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-09 06:59:08 +02:00
[management] switch to libopenapi for managing of openapi-based api (#8056)
* use libopenapi OpenAPI generator Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * added a handler for v1alpha1/peers Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * wire up request validator Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * wired up spec-based validation Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * use sync validation; set base url to v1alpha1 Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * moved stuff around, extracted runtime libopenapu deps into runtime_tooling Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * testing /peers path params Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * fixed tests Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * added user schemas and paths Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * use api validation in tests Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * post-merge fixes Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * deleted tmp command used to test validator integration Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * require request body in post/put requests in order to force validation Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * making linter happy Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * switch to api/v1alpha1 Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * go mod tidy Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * keep the original order of middleware: metrics, cors, then auth Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * return 422 on validation errors Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * cleanups Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * more cleanups Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * v1alpha1 spec cleanups Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * no need for a double-pointer Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * more linter fixes Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * removed more double pointers Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * use di to inject api_v0 and api_v1 http routers Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * do not re-add middleware on repeated call to ApiHandler Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * calling ApiRouter() now also calls ApiV1Router() Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> --------- Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
package apiv1alpha1
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/netbirdio/netbird/shared/management/http/util"
|
||||
"github.com/netbirdio/netbird/shared/management/status"
|
||||
"github.com/pb33f/libopenapi"
|
||||
validator "github.com/pb33f/libopenapi-validator"
|
||||
"github.com/pb33f/libopenapi-validator/config"
|
||||
"github.com/pb33f/libopenapi-validator/errors"
|
||||
"github.com/pb33f/libopenapi/datamodel"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// TODO (dmitri) this needs to be extracted, as it will grow to 500Kb
|
||||
//
|
||||
//go:embed bundle.yaml
|
||||
var bundle []byte
|
||||
|
||||
func CreateV1ApiValidatingMiddleware() (*V1ValidatorMiddleware, error) {
|
||||
doc, err := libopenapi.NewDocumentWithConfiguration(bundle, &datamodel.DocumentConfiguration{
|
||||
Logger: slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
||||
Level: slog.LevelInfo,
|
||||
})),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
model, err := doc.BuildV3Model()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO figure out document validation: rn it's possible to have a spec
|
||||
// that's not entirely correct -- parts of it fail to parse, but silently
|
||||
v := validator.NewValidatorFromV3Model(&model.Model,
|
||||
config.WithoutSecurityValidation(),
|
||||
config.WithStandardBodyDecoders(),
|
||||
config.WithRejectUnsupportedBodyContent(),
|
||||
config.WithRequestDefaults())
|
||||
// v.SetDocument(doc)
|
||||
// v.ValidatePathParams()
|
||||
// if valid, errs := v.ValidateDocument(); !valid {
|
||||
// return nil, fmt.Errorf("error validating OpenAPI doc, %s", errs)
|
||||
// }
|
||||
|
||||
return &V1ValidatorMiddleware{Validator: v}, nil
|
||||
}
|
||||
|
||||
type V1ValidatorMiddleware struct {
|
||||
Validator validator.Validator
|
||||
}
|
||||
|
||||
func (v *V1ValidatorMiddleware) Handler(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
valid, errs := v.Validator.ValidateHttpRequestSync(r)
|
||||
if !valid {
|
||||
validationErrs := make([]string, 0, len(errs))
|
||||
for _, err := range errs {
|
||||
validationErrs = append(validationErrs, validationError(err))
|
||||
}
|
||||
|
||||
log.WithContext(r.Context()).Debugf("error validating request: %s", strings.Join(validationErrs, ", "))
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "invalid request: %s", strings.Join(validationErrs, ", ")), w)
|
||||
|
||||
return
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func validationError(err *errors.ValidationError) string {
|
||||
if err.SchemaValidationErrors != nil {
|
||||
errs := make([]string, 0, len(err.SchemaValidationErrors))
|
||||
for _, e := range err.SchemaValidationErrors {
|
||||
errs = append(errs, fmt.Sprintf("field %s: %s", e.FieldPath, e.Reason))
|
||||
}
|
||||
return fmt.Sprintf("%s: %s", err.Message, strings.Join(errs, ", "))
|
||||
} else {
|
||||
if err.SpecLine > 0 && err.SpecCol > 0 {
|
||||
return fmt.Sprintf("%s, Line: %d, Column: %d", err.Message, err.SpecLine, err.SpecCol)
|
||||
} else {
|
||||
return fmt.Sprint(err.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user