mirror of
https://github.com/hanzoai/gateway.git
synced 2026-10-30 15:04:44 +00:00
Modules declare go 1.27.1 and the build images follow. go fix applied the modernizers; omitzero is not applied, since it changes what the JSON says. Co-authored-by: Hanzo Dev <[email protected]>
121 lines
2.7 KiB
Go
121 lines
2.7 KiB
Go
package jsonschema
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/hanzoai/gateway/v2/internal/lura/config"
|
|
"github.com/hanzoai/gateway/v2/internal/lura/logging"
|
|
"github.com/hanzoai/gateway/v2/internal/lura/proxy"
|
|
"github.com/santhosh-tekuri/jsonschema/v6"
|
|
)
|
|
|
|
const Namespace = "github.com/devopsfaith/krakend-jsonschema"
|
|
|
|
var ErrEmptyBody = &malformedError{err: errors.New("could not validate an empty body")}
|
|
|
|
// ProxyFactory creates an proxy factory over the injected one adding a JSON Schema
|
|
// validator middleware to the pipe when required
|
|
func ProxyFactory(logger logging.Logger, pf proxy.Factory) proxy.FactoryFunc {
|
|
return proxy.FactoryFunc(func(cfg *config.EndpointConfig) (proxy.Proxy, error) {
|
|
next, err := pf.New(cfg)
|
|
if err != nil {
|
|
return proxy.NoopProxy, err
|
|
}
|
|
jschema := configGetter(cfg.ExtraConfig)
|
|
if jschema == nil {
|
|
return next, nil
|
|
}
|
|
|
|
c := jsonschema.NewCompiler()
|
|
c.AddResource("./schema.json", jschema)
|
|
s, err := c.Compile("./schema.json")
|
|
if err != nil {
|
|
logger.Error("[ENDPOINT: " + cfg.Endpoint + "][JSONSchema] Parsing the definition:" + err.Error())
|
|
return next, nil
|
|
}
|
|
logger.Debug("[ENDPOINT: " + cfg.Endpoint + "][JSONSchema] Validator enabled")
|
|
return newProxy(s, next), nil
|
|
})
|
|
}
|
|
|
|
func newProxy(schema *jsonschema.Schema, next proxy.Proxy) proxy.Proxy {
|
|
return func(ctx context.Context, r *proxy.Request) (*proxy.Response, error) {
|
|
if r.Body == nil {
|
|
return nil, ErrEmptyBody
|
|
}
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
r.Body.Close()
|
|
if len(body) == 0 {
|
|
return nil, ErrEmptyBody
|
|
}
|
|
r.Body = io.NopCloser(bytes.NewBuffer(body))
|
|
|
|
b, err := jsonschema.UnmarshalJSON(bytes.NewReader(body))
|
|
if err != nil {
|
|
return nil, &malformedError{err: err}
|
|
}
|
|
|
|
err = schema.Validate(b)
|
|
if err != nil {
|
|
return nil, &validationError{error: err}
|
|
}
|
|
|
|
return next(ctx, r)
|
|
}
|
|
}
|
|
|
|
func configGetter(cfg config.ExtraConfig) any {
|
|
v, ok := cfg[Namespace]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
buf := new(bytes.Buffer)
|
|
if err := json.NewEncoder(buf).Encode(v); err != nil {
|
|
return nil
|
|
}
|
|
schema, err := jsonschema.UnmarshalJSON(buf)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return schema
|
|
}
|
|
|
|
type validationError struct {
|
|
error
|
|
}
|
|
|
|
func (v *validationError) Error() string {
|
|
s := v.error.Error()
|
|
if strings.HasPrefix(s, "jsonschema validation failed with") {
|
|
if ss := strings.SplitN(s, "\n", 2); len(ss) == 2 {
|
|
return ss[1]
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (*validationError) StatusCode() int {
|
|
return http.StatusBadRequest
|
|
}
|
|
|
|
type malformedError struct {
|
|
err error
|
|
}
|
|
|
|
func (m *malformedError) Error() string {
|
|
return m.err.Error()
|
|
}
|
|
|
|
func (*malformedError) StatusCode() int {
|
|
return http.StatusBadRequest
|
|
}
|