go get github.com/slashdevops/qfv@latestgo get -u github.com/slashdevops/qfv@latest
go mod tidyTo pin or move to a specific version:
go get github.com/slashdevops/qfv@v0.1.0QFV (Query Filters Validator) is an Abstract Syntax Tree (AST) parser that validates and parses three common kinds of query expression:
- Fields selection — which fields to include in the response
- Filtering — conditions used to filter results
- Sorting — the order of returned results
Every parser is built from an allow-list of fields, so only fields you explicitly permit can appear in a query. This is the primary defense against invalid or malicious input reaching your database.
All three parsers are safe for concurrent use — build them once and share them across goroutines.
QFV sits between the untrusted query string and your data layer. Build the parsers once at startup; run them per request to reject anything that isn't explicitly allowed before you touch the database.
sequenceDiagram
autonumber
participant C as Client
participant H as HTTP handler
participant P as QFV parsers
participant DB as Database
C->>H: GET /users?fields=…&filter=…&sort=…
H->>P: Parse(fields), Parse(filter), Parse(sort)
alt any parameter invalid
P-->>H: error (unknown field / disallowed operator / bad syntax)
H-->>C: 400 Bad Request
else all valid
P-->>H: FieldsNode + AST + SortNode
H->>DB: build a safe query from the validated output
DB-->>H: rows
H-->>C: 200 OK
end
flowchart LR
boot["App start"] --> build["Build parsers once<br/>NewFilterParser / NewSortParser / NewFieldsParser"]
build --> pool["Shared, concurrency-safe instances"]
pool -. reused per request .-> req1["Request 1"]
pool -. reused per request .-> req2["Request 2"]
pool -. reused per request .-> reqN["Request N"]
package main
import (
"fmt"
"log"
qfv "github.com/slashdevops/qfv"
)
func main() {
// Fields your API is willing to expose.
allowedFields := []string{"first_name", "last_name", "email", "created_at", "updated_at"}
// Build the parsers once; they are safe to reuse concurrently.
sortParser := qfv.NewSortParser(allowedFields)
fieldsParser := qfv.NewFieldsParser(allowedFields)
filterParser := qfv.NewFilterParser(allowedFields)
// Example inputs (typically query-string parameters).
sortInput := "first_name ASC, created_at DESC"
fieldsInput := "first_name, last_name, email"
filterInput := "first_name = 'John' AND last_name = 'Doe'"
sortNode, err := sortParser.Parse(sortInput)
if err != nil {
log.Fatalf("sort validation error: %v", err)
}
fieldsNode, err := fieldsParser.Parse(fieldsInput)
if err != nil {
log.Fatalf("fields validation error: %v", err)
}
filterNode, err := filterParser.Parse(filterInput)
if err != nil {
log.Fatalf("filter validation error: %v", err)
}
fmt.Println("Sort fields:")
for _, f := range sortNode.Fields {
fmt.Printf(" %s %s\n", f.Field, f.Direction)
}
fmt.Println("Requested fields:")
for _, f := range fieldsNode.Fields {
fmt.Printf(" %s\n", f)
}
// The filter parser returns an AST you can walk or render.
fmt.Println("Filter AST:", filterNode.String())
}- Filtering — the full set of operators and predicates
- Configuration — restrict operators and sort directions
- Error Handling — inspecting validation failures