rest-query-parser
                                
                                
                                
                                    rest-query-parser copied to clipboard
                            
                            
                            
                        Query Parser for REST
Query Parser for REST
Query Parser is a library for easy building dynamic SQL queries to Database. It provides a simple API for web-applications which needs to do some filtering throught GET queries. It is a connector between the HTTP handler and the DB engine, and manages validations and translations for user inputs.
Installation
go get -u github.com/timsolov/rest-query-parser
Idea
The idia to write this library comes to me after reading this article: REST API Design: Filtering, Sorting, and Pagination.
And principles enumerated in article I considered very useful and practical to use in our project with amount of listings with different filtering.
Fast start
See cmd/main.go and tests for more examples.
    package main
    
    import (
        "errors"
        "fmt"
        "net/url"
    
        rqp "github.com/timsolov/rest-query-parser"
    )
    
    func main() {
        url, _ := url.Parse("http://localhost/?sort=+name,-id&limit=10&id=1&i[eq]=5&s[eq]=one&email[like]=*tim*|name[like]=*tim*")
        q, _ := rqp.NewParse(url.Query(), rqp.Validations{
            "limit:required": rqp.MinMax(10, 100),  // limit must present in the Query part and must be between 10 and 100 (default: Min(1))
            "sort":           rqp.In("id", "name"), // sort could be or not in the query but if it is present it must be equal to "in" or "name"
            "s":      rqp.In("one", "two"), // filter: s - string and equal
            "id:int": nil,                  // filter: id is integer without additional validation
            "i:int": func(value interface{}) error { // filter: custom func for validating
                if value.(int) > 1 && value.(int) < 10 {
                    return nil
                }
                return errors.New("i: must be greater then 1 and lower then 10")
            },
            "email": nil,
            "name":  nil,
        })
        fmt.Println(q.SQL("table")) // SELECT * FROM table WHERE id = ? AND i = ? AND s = ? AND (email LIKE ? OR name LIKE ?) ORDER BY name, id DESC LIMIT 10
        fmt.Println(q.Where())      // id = ? AND i = ? AND s = ? AND (email LIKE ? OR name LIKE ?)
        fmt.Println(q.Args())       // [1 5 one %tim% %tim%]
        q.AddValidation("fields", rqp.In("id", "name"))
        q.SetUrlString("http://localhost/?fields=id,name&limit=10")
        q.Parse()
        fmt.Println(q.SQL("table")) // SELECT id, name FROM table ORDER BY id LIMIT 10
        fmt.Println(q.FieldsSQL())  // id, name
        fmt.Println(q.Args())       // []
    }
Top level fields:
fields- fields for SELECT clause separated by comma (",") Eg.&fields=id,name. If nothing provided will use "*" by default. Attention! If you want to use this filter you have to define validation func for it. Userqp.In("id", "name")func for limit fields for your query.sort- sorting fields list separated by comma (","). Must be validated too. Could include prefix +/- which means ASC/DESC sorting. Eg.&sort=+id,-namewill printORDER BY id, name DESC. You have to filter fields in this parameter by addingrqp.In("id", "name").limit- is limit for LIMIT clause. Should be greater then 0 by default. Definition of the validation forlimitis not required. But you may userqp.Max(100)to limit top threshold.offset- is offset for OFFSET clause. Should be greater then or equal to 0 by default. Definition of the validation foroffsetis not required.
Validation modificators:
:required- parameter is required. Must present in the query string. Raise error if not.:int- parameter must be convertable to int type. Raise error if not.:bool- parameter must be convertable to bool type. Raise error if not.
Supported types
string- the default type for all provided filters if not specified another. Could be compared byeq, ne, gt, lt, gte, lte, like, ilike, nlike, nilike, in, nin, is, notmethods (nlike, nilikemeansNOT LIKE, NOT ILIKErespectively,in, ninmeansIN, NOT INrespectively,is, notfor comparison to NULLIS NULL, IS NOT NULL).int- integer type. Must be specified with tag ":int". Could be compared byeq, ne, gt, lt, gte, lte, in, ninmethods.bool- boolean type. Must be specified with tag ":bool". Could be compared byeqmethod.
Date usage
This is simple example to show logic which you can extend.
    import (
        "fmt"
        "net/url"
        validation "github.com/go-ozzo/ozzo-validation/v4"
    )
    func main() {
        url, _ := url.Parse("http://localhost/?create_at[eq]=2020-10-02")
        q, _ := rqp.NewParse(url.Query(), rqp.Validations{
            "created_at": func(v interface{}) error {
                s, ok := v.(string)
                if !ok {
                    return rqp.ErrBadFormat
                }
                return validation.Validate(s, validation.Date("2006-01-02"))
            },
        })
        q.ReplaceNames(rqp.Replacer{"created_at": "DATE(created_at)"})
        fmt.Println(q.SQL("table")) // SELECT * FROM table WHERE DATE(created_at) = ?
    }
                                                                                                
