swaggest / rest

Web services with OpenAPI and JSON Schema done quick in Go
https://pkg.go.dev/github.com/swaggest/rest
MIT License
335 stars 17 forks source link

Validate JSON body before unmarshalling into input type #203

Open Leskodamus opened 3 days ago

Leskodamus commented 3 days ago

Fixes #202

Instead of first reading the JSON into the input object, perform validation to return appropriate error messages in case of trying to parse an invalid type mapping, e.g. int to string.

Originally you would get failed to decode json: json: cannot unmarshall... error and now it returns more context instead, for example:

{
  "status": "INVALID_ARGUMENT",
  "error": "invalid argument: validation failed",
  "context": {
    "body": [
      "#/name: expected string, but got number"
    ]
  }
}
Leskodamus commented 2 days ago

Ugh, of course it does not perform body validation in ValidateJSONBody(jsonBody []byte) error if the schema is not found in rest.ParamInBody or the schema is nil:

func (v *Validator) ValidateJSONBody(jsonBody []byte) error {
  name := "body"

  schema, found := v.inNamedSchemas[rest.ParamInBody][name]
  if !found || schema == nil {
    return nil
  }
  // ...
}

It only seems to have this value if a constraint has been given to a type. In my situation it was using uint for a field that automatically set a >=0 constraint and therefore a schema was found. Changing that to int and just using a JSON tag does not make this implementation possible.

For example:

// No validation will happen.
type ExampleIn struct {
  Value int `json:"value"`
  Name string `json:"unit"`
}

// This does get validated.
type ExampleIn struct {
  Value int `json:"value" minimum:"1"`
  Name string `json:"unit"`
}

Maye we could add a way to have schema data even if it is trivial? For example in collector.go in func (c *Collector) jsonSchemaCallback(validator rest.JSONSchemaValidator, r openapi.Reflector) openapi.JSONSchemaCallback {}:

// ...
// It'd be great to have schema data even if it is trivial.
if schema == nil || schema.IsTrivial(r.ResolveJSONSchemaRef) {
  if err := validator.AddSchema(rest.ParamIn(in), paramName, nil, required); err != nil {
    return fmt.Errorf("add validation schema %s: %w", loc, err)
}

  return nil
}

Then, either always validate if a schema is not nil - which does indeed affect performance if it has to go through every check although it is not necessary, or implement a separate validation for just decoding JSON bytes into a structure.