4

I need to validate that my http request has two parameters, Start and End. Currently, I set a default value that should not appear as either of the parameters and check for it along with other invalid values. However, this feels like a hack. What should be the proper way to do this?

Here is my code:

type Request struct {
    Start int `json: "start"`
    End int `json: "end"`
}


func HandlePost(w http.ResponseWriter, r *http.Request) {
    body , _ := ioutil.ReadAll(r.Body)
    reqData := Request{Start: -1, End: -1} // < whats the correct way to do this
    json.Unmarshal(body, &reqData)  

    if reqData.Start < 0 && reqData.End < 0 {
        w.WriteHeader(http.StatusBadRequest)
        return
    }
    // rest of the logic
}
Learnerng
  • 53
  • 1
  • 5
  • 2
    Usually you would use pointers for this: if no value is passed in the request then the fields will remain `nil`. – mkopriva Aug 25 '18 at 09:04
  • Possible duplicate of [Assigning null to JSON fields instead of empty strings in Golang](https://stackoverflow.com/questions/31048557/assigning-null-to-json-fields-instead-of-empty-strings-in-golang) – Flimzy Aug 25 '18 at 10:24
  • Thanks, @mkopriva and zaky had the answer I was looking for. The other answers used external libraries which may not be so idiomatic. Could either of you make an answer and I will flag it as the accepted answer? – Learnerng Aug 25 '18 at 10:50
  • Note the question actually has nothing to do with HTTP at all. It's only about detecting the absence of a JSON value. – Flimzy Aug 25 '18 at 15:08

3 Answers3

3

You can use https://github.com/asaskevich/govalidator for basic way of validating the request. But in case you want something more sophisticated, you need to write your own custom validator function. e.g.

type Request struct {
    Start int `json: "start"`
    End int `json: "end"`
}

func (a *Request) validate() url.Values {
    err := url.Values{}

    // Write your own validation rules
    if a.Start < 0 {
        err.Add("Start", "Start cannot be less than 0");
    }

    return err;
}

func handler(w http.ResponseWriter, r *http.Request) {
    requestBody := &Request{}

    defer r.Body.Close()
    if err := json.NewDecoder(r.Body).Decode(requestBody); err != nil {
        panic(err)
    }

    if errors := requestBody.validate(); len(errors) > 0 {
        err := map[string]interface{}{"validationError": errors}
        w.Header().Set("Content-type", "application/json")
        w.WriteHeader(http.StatusBadRequest)
        json.NewEncoder(w).Encode(err)
    }

    fmt.Fprint(w, "success request scenario!")
}
Aditya Singh
  • 14,688
  • 12
  • 42
  • 64
0

You can use https://github.com/buger/jsonparser getInt. You'll get an error if the json is missing the expected key.

I recommend using benchmark and not decided by the code beauty or any other hunch

ZAky
  • 1,036
  • 7
  • 21
  • As @mkopriva mentioned, the idiomatic way is to use a pointer, not to use a library. By using a pointer, the value will be `nil` instead of set to 0 when not specified. – Ullaakut Aug 25 '18 at 10:00
  • 2
    Pointer may be the idiomatic way when you want to set a default value when a value is not provided. When you want to validate it may be the wrong way. Let's say you have a big list of properties, by using pointer you unmarshal the entire struct just to find out it's not valid. Lets also assum you have several fields to validate, in this case you can check first for the most likely fields that may be missing and return much faster – ZAky Aug 25 '18 at 10:26
  • Ah my bad, I misunderstood the question in the first place, using a validator library is better for this yeah. Sorry about that :p – Ullaakut Aug 25 '18 at 11:14
  • @Ullaakut Then revert your vote down :) If its you of course. Thank you. – ZAky Aug 25 '18 at 12:13
0

Here's another way to validate structures using struct tags and pointers. Note that if 0 is a valid thing to pass, then this solution will not work. omitempty considers the 0 value to be empty. If you want this to work will considering 0 to be valid remove the pointers and modify the IsValid method

package main

import (
    "encoding/json"
    "fmt"
)

type Request struct {
    Start *int `json: "start,omitempty"`
    End   *int `json: "end,omitempty"`
}

func (r Request) IsValid() (bool, error) {
    if r.Start == nil {
        return false, fmt.Errorf("start is missing")
    }

    if r.End == nil {
        return false, fmt.Errorf("end is missing")
    }

    return true, nil
}

var (
    invalidStartb = `{"end": 1}`
    invalidEndb   = `{"start": 1}`
    valid         = `{"start": 1, "end": 1}`
)

func main() {
    var r Request

    _ = json.Unmarshal([]byte(invalidStartb), &r)
    fmt.Println(r.IsValid())

    r = Request{}
    _ = json.Unmarshal([]byte(invalidEndb), &r)
    fmt.Println(r.IsValid())

    r = Request{}
    _ = json.Unmarshal([]byte(valid), &r)
    fmt.Println(r.IsValid())

}

runnable version here https://goplay.space/#Z0eqLpEHO37

reticentroot
  • 3,436
  • 2
  • 21
  • 38