goyave icon indicating copy to clipboard operation
goyave copied to clipboard

JSON Schema Validator

Open snadrus opened this issue 4 years ago • 5 comments

Other than your input validator, consider the JSON Schema validator (jsonschema.org). Although similar, it has the advantage of write-once, run anywhere. The same validation schema could run on the browser before bothering the server. There would be no challenge keeping the two in synchronization: just use the same validator file.

snadrus avatar Sep 14 '20 20:09 snadrus

Hello, can you write a more in-depth description of how you pictured it would work? How would it be used, with examples, etc. Would it be a middleware? Would it be something detached from the current validation system or an extension of it?

System-Glitch avatar Sep 14 '20 20:09 System-Glitch

Today (from example):

var (
    StoreRequest validation.RuleSet = validation.RuleSet{
        "name":  {"required", "string", "between:3,50"},
        "price": {"required", "numeric", "min:0.01"},
        "image": {"nullable", "file", "image", "max:2048", "count:1"},
    }

    // ...
)
func Routes(router *goyave.Router) {
   router.Post("/product", product.Store).Validate(product.StoreRequest)
}

With a schema validator:

var (
    StoreRequest = ` 
{
 "$id": "https://example.com/productPost.schema.json", 
 "$schema": "http://json-schema.org/draft-07/schema#",
 "title": "ProductPost", 
 "type": "object", 
 "required": [ "name", "price" ],
 "properties": {
   "name": {
      "type": "string", 
      "minimum": 3, 
      "maximum": 50
   },
   "price": {
      "type": "number",
      "minimum": 0.01
   },
   "image": {
      "type": "string", 
      "maximum": 2048,
   }
 }
}`
)
func Routes(router *goyave.Router) {
    // Uses the Go JSON schema validator:
   router.Post("/product", product.Store).ValidateWithJSONSchema(StoreRequest)

   // So the browser can run the Javascript version of the JSON schema validator too:
   router.Get("/productPost.schema.json", func(response *goyave.Response, request *goyave.Request){
      response.String(http.StatusOK, StoreRequest)
   })
}

The validators immediately tell the user what went wrong in the browser before the form posts to the user. It saves the user from waiting for a POST to process before finding out there's an error. The dev work is similar since the validators produce an error message like "name too long, max 50". To use, a Front-End dev only needs to:

  • get this schema,
  • Include
  • Use the Form's onSubmit hook to cancel POST if there's an error string returned from the validator. Ex:
function showErr(msg) {
   document.getElementByID('message').innerText = msg;
   return (msg=='');    // submit if no error to show
}
<form onSubmit="showErr( jsonSchema.Valid( await fetch('/productPost.schema.json'), $(this).serialize() ) )">

snadrus avatar Sep 14 '20 21:09 snadrus

@snadrus Sorry for the late response.

This has a caveat: the validator converts data into the type you are expecting to receive. For example, validating a URL converts the request data (a string) to a *url.URL. How would that work with a JSON schema validation? Validation would not guarantee data types anymore, unless you can think of a solution to remedy this?

It looks like you are trying to ease the work of the front-end developer while having to avoid duplicating validation settings. Maybe we could work on a module able to convert RuleSet into a JSON schema? (for example: RuleSet.ToJSONSchema())

System-Glitch avatar Sep 30 '20 18:09 System-Glitch

Implicitly if my handler is called then I can presume a safe conversion from string to *url.Url if that's what the JSON validation guarantees.

This has the unfortunate downside of potentially creating a *url.Url twice from the same data, but it shouldn't be a burden on GC since the validator's copy should have stayed in-stack. It will cause additional CPU use, but that's arguably less server CPU now that negative legit cases are handled in the browser. And the cost of building a *url.Url is quite low, so handler response time is not at risk.

On Wed, Sep 30, 2020 at 2:40 PM SystemGlitch [email protected] wrote:

This has a caveat: the validator converts data into the type you are expecting to receive. For example, validating a URL converts the request data (a string) to a *url.URL. How would that work with a JSON schema validation? Validation would not guarantee data types anymore, unless you can think of a solution to remedy this?

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/System-Glitch/goyave/issues/111#issuecomment-701571598, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAOU4LVXXOLCJOTSXFIYTADSIN3RLANCNFSM4RMA3LTA .

snadrus avatar Sep 30 '20 19:09 snadrus

The problem with this is that we would have two way to validate incoming requests. One would not convert automatically so you would have to do it in the controller handler, the other does and you can retrieve it easily with the request object accessor.

Here is my suggestion:

On the backend, keep the same validation mechanism with RuleSet. But add a method to it to convert it easily to JSON Schema (RuleSet.ToJSONSchema()). This method would be used inside the handler for the route serving the JSON schema. (A router method could also be implemented to make this easier: router.JSONSchema(product.InsertRequest). This way, there is no duplicate validation settings, data conversion is still effective, front-end is guaranteed to be validated exactly the same way as the backend, and without any performance hit. Implementation would be simpler and there would be less feature fragmentation.

Would that work for you?

System-Glitch avatar Oct 01 '20 08:10 System-Glitch