rqp

package module
v2.1.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 12, 2023 License: Apache-2.0 Imports: 11 Imported by: 0

README

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.

GoDoc Coverage Status

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{
            "page_size: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. 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) = ?
    }

Documentation

Index

Examples

Constants

View Source
const (
	FieldTypeJson        = "json"
	FieldTypeObject      = "object"
	FieldTypeInt         = "int"
	FieldTypeBool        = "bool"
	FieldTypeCustom      = "custom"
	FieldTypeFloat       = "float"
	FieldTypeString      = "string"
	FieldTypeTime        = "time"
	FieldTypeObjectArray = "objectarray"
	FieldTypeIntArray    = "intarray"
	FieldTypeStringArray = "stringarray"
	FieldTypeFloatArray  = "floatarray"
)
View Source
const NULL = "NULL"

NULL constant

Variables

View Source
var (
	ErrRequired           = NewError("required")
	ErrBadFormat          = NewError("bad format")
	ErrEmptyValue         = NewError("empty value")
	ErrUnknownMethod      = NewError("unknown method")
	ErrNotInScope         = NewError("not in scope")
	ErrSimilarNames       = NewError("similar names of keys are not allowed")
	ErrMethodNotAllowed   = NewError("method are not allowed")
	ErrFilterNotAllowed   = NewError("filter are not allowed")
	ErrFilterNotFound     = NewError("filter not found")
	ErrValidationNotFound = NewError("validation not found")
)

Errors list:

Functions

This section is empty.

Types

type DatabaseField

type DatabaseField struct {
	Name     string
	Table    string
	Type     FieldType
	IsNested bool
	Alias    string
}

func (DatabaseField) IsSortable added in v2.1.2

func (df DatabaseField) IsSortable() bool

type Error

type Error struct {
	// contains filtered or unexported fields
}

Error special rqp.Error type

func NewError

func NewError(msg string) *Error

NewError constructor for internal errors

func (*Error) Error

func (e *Error) Error() string

type FieldType

type FieldType string

type Filter

type Filter struct {
	Key               string // key from URL (eg. "id[eq]")
	ParameterizedName string // after applying enhancements to allow nesting
	QueryName         string // name of filter, takes from Key (eg. "id")
	Method            Method // compare method, takes from Key (eg. EQ)
	Value             interface{}
	OR                StateOR
	DbField           DatabaseField
}

Filter represents a filter defined in the query part of URL

func (*Filter) Args

func (f *Filter) Args() ([]interface{}, error)

Args returns arguments slice depending on filter condition

func (*Filter) Where

func (f *Filter) Where() (string, error)

Where returns condition expression

type Method

type Method string

Method is a compare method type

var (
	EQ     Method = "EQ"
	NE     Method = "NE"
	GT     Method = "GT"
	LT     Method = "LT"
	GTE    Method = "GTE"
	LTE    Method = "LTE"
	LIKE   Method = "LIKE"
	ILIKE  Method = "ILIKE"
	NLIKE  Method = "NLIKE"
	NILIKE Method = "NILIKE"
	IS     Method = "IS"
	NOT    Method = "NOT"
	IN     Method = "IN"
	NIN    Method = "NIN"
)

Compare methods:

type NonDatabaseField added in v2.1.4

type NonDatabaseField struct {
	Type     FieldType
	Sortable bool
}

type Query

type Query struct {
	QueryFields []string
	Page        int
	PageSize    int
	Sorts       []Sort
	Filters     []*Filter

	Error error
	// contains filtered or unexported fields
}

Query the main struct of package

func New

func New() *Query

New creates new instance of Query

func NewParse

func NewParse(q url.Values, v Validations, qdbm QueryDatabaseMap) (*Query, error)

NewParse creates new Query instance and Parse it

func NewQV

func NewQV(q url.Values, v Validations, qdbm QueryDatabaseMap) *Query

NewQV creates new Query instance with parameters

func (*Query) AddORFilters

func (q *Query) AddORFilters(fn func(query *Query)) *Query

AddORFilters adds multiple filter into one `OR` statement inside parenteses. E.g. (firstname ILIKE ? OR lastname ILIKE ?)

Example
q := New().AddQueryFilter("test", EQ, "ok")
q.AddORFilters(func(query *Query) {
	query.AddQueryFilter("firstname", ILIKE, "*hello*")
	query.AddQueryFilter("lastname", ILIKE, "*hello*")
})
q.SQL("table") // SELECT * FROM table WHERE test = ? AND (firstname ILIKE ? OR lastname ILIKE ?)
Output:

func (*Query) AddQueryDbFieldMapping

func (q *Query) AddQueryDbFieldMapping(queryName string, dbField DatabaseField) *Query

func (*Query) AddQueryField

func (q *Query) AddQueryField(queryName string) *Query

AddField adds field to SELECT statement

func (*Query) AddQueryFields added in v2.0.5

func (q *Query) AddQueryFields(queryNames ...string) *Query

AddFields adds fields to SELECT statement

func (*Query) AddQueryFilter

func (q *Query) AddQueryFilter(queryName string, m Method, value interface{}) *Query

AddFilter adds a filter to Query

func (*Query) AddQuerySortBy

func (q *Query) AddQuerySortBy(querySortBy string, desc bool) *Query

AddQuerySortBy adds an ordering rule to Query

func (*Query) AddQueryValidation

func (q *Query) AddQueryValidation(NameAndTags string, v ValidationFunc) *Query

AddValidation adds a validation to Query

func (*Query) AllowNonDatabaseFields added in v2.1.4

func (q *Query) AllowNonDatabaseFields(filters map[string]NonDatabaseField) *Query

set behavior for Parser to not raise ErrFilterNotAllowed for these undefined filters or not

func (*Query) Args

func (q *Query) Args(tables ...string) []interface{}

Args returns slice of arguments for WHERE statement

func (*Query) GetQueryFilter

func (q *Query) GetQueryFilter(queryName string) (*Filter, error)

GetFilter returns filter by name

func (*Query) HaveQueryField

func (q *Query) HaveQueryField(queryName string) bool

HaveQueryField returns true if request asks for specified field

func (*Query) HaveQueryFieldsOnAnyTables

func (q *Query) HaveQueryFieldsOnAnyTables(tables ...string) bool

HaveQueryFieldsOnAnyTables returns true if request asks for a field on ANY of specified tables

func (*Query) HaveQueryFieldsOnTable

func (q *Query) HaveQueryFieldsOnTable(table string) bool

HaveQueryFieldsOnTable returns true if request asks for a field on the specified table

func (*Query) HaveQueryFilter

func (q *Query) HaveQueryFilter(queryName string) bool

HaveFilter returns true if request contains some filter

func (*Query) HaveQueryFiltersOnAnyTables

func (q *Query) HaveQueryFiltersOnAnyTables(tables ...string) bool

HaveQueryFiltersOnAnyTables returns true if request filters by a field on ANY of specified tables

func (*Query) HaveQueryFiltersOnTable

func (q *Query) HaveQueryFiltersOnTable(table string) bool

HaveQueryFiltersOnTable returns true if request filters by a field on the specified table

func (*Query) HaveQuerySortBy

func (q *Query) HaveQuerySortBy(querySortBy string) bool

HaveSortBy returns true if request contains sorting by specified in by field name

func (*Query) HaveQuerySortByOnAnyTables

func (q *Query) HaveQuerySortByOnAnyTables(tables ...string) bool

HaveQuerySortByOnAnyTables returns true if request sorts by a field on ANY of specified tables

func (*Query) HaveQuerySortByOnTable

func (q *Query) HaveQuerySortByOnTable(table string) bool

HaveQuerySortByOnTable returns true if request sorts by a field on the specified table

func (*Query) IgnoreUnknownFilters

func (q *Query) IgnoreUnknownFilters(i bool) *Query

IgnoreUnknownFilters set behavior for Parser to raise ErrFilterNotAllowed to undefined filters or not

func (*Query) LIMIT

func (q *Query) LIMIT() string

LIMIT returns word LIMIT with number

Return example: ` LIMIT 100`

func (*Query) OFFSET

func (q *Query) OFFSET() string

OFFSET returns word OFFSET with number

Return example: ` OFFSET 0`

func (*Query) ORDER

func (q *Query) ORDER() string

ORDER returns words ORDER BY with list of elements for sorting you can use +/- prefix to specify direction of sorting (+ is default, apsent is +)

Return example: ` ORDER BY id DESC, email`

func (*Query) Order

func (q *Query) Order() string

Order returns list of elements for ORDER BY statement you can use +/- prefix to specify direction of sorting (+ is default) return example: `id DESC, email`

func (*Query) Parse

func (q *Query) Parse() (err error)

Parse parses the query of URL as query you can use standart http.Request query by r.URL.Query()

func (*Query) RemoveQueryFilter

func (q *Query) RemoveQueryFilter(name string) error

RemoveFilter removes the filter by name

func (*Query) RemoveQueryValidation

func (q *Query) RemoveQueryValidation(NameAndOrTags string) error

RemoveValidation remove a validation from Query You can provide full name of filter with tags or only name of filter: RemoveValidation("id:int") and RemoveValidation("id") are equal

func (*Query) SELECT

func (q *Query) SELECT(tables ...string) string

SELECT returns word SELECT with fields from Filter "fields" separated by comma (",") from URL-Query or word SELECT with star ("*") if nothing provided

Return examples:

When "fields" empty or not provided: `SELECT *`.

When "fields=id,email": `SELECT id, email`.

func (*Query) SQL

func (q *Query) SQL(table string) string

SQL returns whole SQL statement

func (*Query) Select

func (q *Query) Select(tables ...string) string

Select returns elements list separated by comma (",") for querying in SELECT statement or a star ("*") if nothing provided

Return examples:

When "fields" empty or not provided: `*`

When "fields=id,email": `id, email`

func (*Query) SetDelimiterIN

func (q *Query) SetDelimiterIN(d string) *Query

SetDelimiterIN sets delimiter for values of filters

func (*Query) SetDelimiterOR

func (q *Query) SetDelimiterOR(d string) *Query

SetDelimiterOR sets delimiter for OR filters in query part of URL

func (*Query) SetPage

func (q *Query) SetPage(page int) *Query

func (*Query) SetPageSize

func (q *Query) SetPageSize(pageSize int) *Query

func (*Query) SetQueryDbFieldsMap

func (q *Query) SetQueryDbFieldsMap(m QueryDatabaseMap) *Query

func (*Query) SetUrlQuery

func (q *Query) SetUrlQuery(query url.Values) *Query

SetUrlQuery change url in the Query for parsing uses when you need provide Query from http.HandlerFunc(w http.ResponseWriter, r *http.Request) you can do q.SetUrlValues(r.URL.Query())

func (*Query) SetUrlString

func (q *Query) SetUrlString(Url string) error

SetUrlString change url in the Query for parsing uses when you would like to provide raw URL string to parsing

func (*Query) SetValidations

func (q *Query) SetValidations(v Validations) *Query

SetValidations change validations rules for the instance

func (*Query) UsesAnyTables

func (q *Query) UsesAnyTables(tables ...string) bool

UsesAnyTables returns true if the query uses a database field on ANY of the tables

func (*Query) UsesTable

func (q *Query) UsesTable(table string) bool

UsesTables returns true if the query uses a database field on the table

func (*Query) WHERE

func (q *Query) WHERE(tables ...string) string

WHERE returns list of filters for WHERE SQL statement with `WHERE` word

Return example: ` WHERE id > 0 AND email LIKE 'some@email.com'`

func (*Query) Where

func (q *Query) Where(tables ...string) string

Where returns list of filters for WHERE statement return example: `id > 0 AND email LIKE 'some@email.com'`

type QueryDatabaseMap added in v2.1.4

type QueryDatabaseMap map[string]DatabaseField

type Sort

type Sort struct {
	QuerySortBy     string
	ByParameterized string
	Desc            bool
}

Sort is ordering struct

type StateOR

type StateOR byte
const (
	NoOR StateOR = iota
	StartOR
	InOR
	EndOR
)

type ValidationFunc

type ValidationFunc func(value interface{}) error

ValidationFunc represents validator for Filters

func In

func In(values ...any) ValidationFunc

func Max

func Max(max int) ValidationFunc

Max validation if value lower or equal then max

func Min

func Min(min int) ValidationFunc

Min validation if value greater or equal then min

func MinMax

func MinMax(min, max int) ValidationFunc

MinMax validation if value between or equal min and max

func Multi

func Multi(values ...ValidationFunc) ValidationFunc

Multi multiple validation func usage: Multi(Min(10), Max(100))

func NotEmpty

func NotEmpty() ValidationFunc

NotEmpty validation if string value length more then 0

type Validations

type Validations map[string]ValidationFunc

Validations type replacement for map. Used in NewParse(), NewQV(), SetValidations()

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL