Giter Club home page Giter Club logo

rest-query-parser's Introduction

Query Parser for REST

awesome-go

GoDoc Coverage Status Awesome

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.Select())     // 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. Use rqp.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,-name will print ORDER BY id, name DESC. You have to filter fields in this parameter by adding rqp.In("id", "name").
  • limit - is limit for LIMIT clause. Should be greater then 0 by default. Definition of the validation for limit is not required. But you may use rqp.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 for offset is 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 by eq, ne, gt, lt, gte, lte, like, ilike, nlike, nilike, in, nin, is, not methods (nlike, nilike means NOT LIKE, NOT ILIKE respectively, in, nin means IN, NOT IN respectively, is, not for comparison to NULL IS NULL, IS NOT NULL).
  • int - integer type. Must be specified with tag ":int". Could be compared by eq, ne, gt, lt, gte, lte, in, nin methods.
  • bool - boolean type. Must be specified with tag ":bool". Could be compared by eq method.

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) = ?
    }

rest-query-parser's People

Contributors

dennigogo avatar gerricom avatar timsolov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

rest-query-parser's Issues

Removing filters with OR statements leads to incorrect Where construction

All cases in single test case set

func Test_RemoveOrEntries(t *testing.T) {
	type testCase struct {
		name string
		urlQuery string
		filterToRemove string
		wantWhere             string
	}
	tests := []testCase{
		{
			name: "should fix OR statements after removing EndOR filter with 2 items",
			urlQuery: "?test1[eq]=test10|test2[eq]=test10",
			filterToRemove: "test2",
			wantWhere: "test1 = ?",
		},
		{
			name: "should fix OR statements after removing StartOR filter with 2 items",
			urlQuery: "?test1[eq]=test10|test2[eq]=test10",
			filterToRemove: "test1",
			wantWhere: "test2 = ?",
		},
		{
			name: "should fix OR statements after removing StartOR filter with 3 items",
			urlQuery: "?test1[eq]=test10|test2[eq]=test10|test3[eq]=test10",
			filterToRemove: "test1",
			wantWhere: "(test2 = ? OR test3 = ?)",
		},
		{
			name: "should fix OR statements after removing EndOR filter with 3 items",
			urlQuery: "?test1[eq]=test10|test2[eq]=test10|test3[eq]=test10",
			filterToRemove: "test3",
			wantWhere: "(test1 = ? OR test2 = ?)",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Arrange
			URL, _ := url.Parse(tt.urlQuery)
			q := rqp.NewQV(nil, rqp.Validations{
				"test1": nil,
				"test2": nil,
				"test3": nil,
			})
			_ = q.SetUrlQuery(URL.Query()).Parse()

			// Act
			_ = q.RemoveFilter(tt.filterToRemove)

			// Assert
			assert.Equal(t, tt.wantWhere, q.WHERE())
		})
	}
}

Readme Example gives me an error

Hello! NIce library, very comprehensive functions. I've tried but i got an error from example in Readme.

q2, _ := rqp.NewParse(r.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,
	})

URL looks like: ?sort=+name,-id&limit=10&id=1&i[eq]=5&s[eq]=one&email[like]=*tim*|name[like]=*tim*

Error: s[eq]: filter not found

If i remove s[eq] parameter from url query, then i got an error: sort: validation not found

If i remove sort parameter from url query, then i got an error: id: filter not found

Can you tell what i'm doing wrong?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.