Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

expose field object publicly #52

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 39 additions & 10 deletions rql.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
//go:generate easyjson -omit_empty -disallow_unknown_fields -snake_case rql.go

// Query is the decoded result of the user input.
//
//easyjson:json
type Query struct {
// Limit must be > 0 and <= to `LimitMaxValue`.
Expand Down Expand Up @@ -73,7 +74,6 @@ type Query struct {
// return nil, err
// }
// return users, nil
//
type Params struct {
// Limit represents the number of rows returned by the SELECT statement.
Limit int
Expand Down Expand Up @@ -105,7 +105,7 @@ func (p ParseError) Error() string {
}

// field is a configuration of a struct field.
type field struct {
type FieldMeta struct {
// Name of the column.
Name string
// Has a "sort" option in the tag.
Expand All @@ -114,6 +114,9 @@ type field struct {
Filterable bool
// All supported operators for this field.
FilterOps map[string]bool
}
type Field struct {
*FieldMeta
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not inline its fields inside the Field type?

// Validation for the type. for example, unit8 greater than or equal to 0.
ValidateFn func(interface{}) error
// ConvertFn converts the given value to the type value.
Expand All @@ -124,7 +127,7 @@ type field struct {
// It is safe for concurrent use by multiple goroutines except for configuration changes.
type Parser struct {
Config
fields map[string]*field
fields map[string]*Field
}

// NewParser creates a new Parser. it fails if the configuration is invalid.
Expand All @@ -134,14 +137,31 @@ func NewParser(c Config) (*Parser, error) {
}
p := &Parser{
Config: c,
fields: make(map[string]*field),
fields: make(map[string]*Field),
}
if err := p.init(); err != nil {
return nil, err
}
return p, nil
}

// Does not use config.Model, gets config from Fields)
func NewParserF(c Config, fields []*Field) (*Parser, error) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to: NewFieldsParser or add a new option as Config.Fields.

Also, an error should return if c.Model was set as the two are mutually exclusive.

if err := c.defaults(); err != nil {
return nil, err
}

m := make(map[string]*Field, len(fields))
for _, v := range fields {
m[v.Name] = v
}
p := &Parser{
Config: c,
fields: m,
}
return p, nil
}

// MustNewParser is like NewParser but panics if the configuration is invalid.
// It simplifies safe initialization of global variables holding a resource parser.
func MustNewParser(c Config) *Parser {
Expand Down Expand Up @@ -197,13 +217,20 @@ func (p *Parser) ParseQuery(q *Query) (pr *Params, err error) {
return
}

func (p *Parser) GetFields() []*Field {
fields := make([]*Field, 0, len(p.fields))
for _, v := range p.fields {
fields = append(fields, v)
}
return fields
}
Comment on lines +220 to +226
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func (p *Parser) GetFields() []*Field {
fields := make([]*Field, 0, len(p.fields))
for _, v := range p.fields {
fields = append(fields, v)
}
return fields
}
func (p *Parser) Fields() []*Field {
fields := make([]*Field, 0, len(p.fields))
for _, v := range p.fields {
fields = append(fields, v)
}
return fields
}

Why this method is needed? If it's for testing, let's use the existing public API instead.


// Column is the default function that converts field name into a database column.
// It used to convert the struct fields into their database names. For example:
//
// Username => username
// FullName => full_name
// HTTPCode => http_code
//
func Column(s string) string {
var b strings.Builder
for i := 0; i < len(s); i++ {
Expand Down Expand Up @@ -257,10 +284,12 @@ func (p *Parser) init() error {
// parseField parses the given struct field tag, and add a rule
// in the parser according to its type and the options that were set on the tag.
func (p *Parser) parseField(sf reflect.StructField) error {
f := &field{
Name: p.ColumnFn(sf.Name),
CovertFn: valueFn,
FilterOps: make(map[string]bool),
f := &Field{
FieldMeta: &FieldMeta{
Name: p.ColumnFn(sf.Name),
FilterOps: make(map[string]bool),
},
CovertFn: valueFn,
}
layout := time.RFC3339
opts := strings.Split(sf.Tag.Get(p.TagName), ",")
Expand Down Expand Up @@ -438,7 +467,7 @@ func (p *parseState) relOp(op Op, terms []interface{}) {
}
}

func (p *parseState) field(f *field, v interface{}) {
func (p *parseState) field(f *Field, v interface{}) {
terms, ok := v.(map[string]interface{})
// default equality check.
if !ok {
Expand Down
55 changes: 55 additions & 0 deletions rql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1021,3 +1021,58 @@ func mustParseTime(layout, s string) time.Time {
t, _ := time.Parse(layout, s)
return t
}

func TestGetFields(t *testing.T) {
tests := []struct {
name string
conf Config
wantOut []*Field
}{
{
name: "get fields",
conf: Config{
Model: struct {
SomeName string `rql:"filter"`
}{},
},
wantOut: []*Field{
&Field{
FieldMeta: &FieldMeta{
Name: "some_name",
Sortable: false,
Filterable: true,
},
// Column: "some_name",
// AvailableOps: []string{"$eq", "$neq", "$lt", "$lte", "$gt", "$gte", "$like"},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p, err := NewParser(tt.conf)
if err != nil {
t.Fatalf("failed to build parser: %v", err)
}
out := p.GetFields()
assertFieldsEqual(t, out, tt.wantOut)
})
}
}

func assertFieldsEqual(t *testing.T, got []*Field, want []*Field) {
if len(got) != len(want) {
t.Fatalf("got %v, wanted %v", got, want)
}
for i := 0; i < len(got); i++ {
if got[i].Filterable != want[i].Filterable {
t.Fatalf("Filterable got:%v want: %v", got[i].Filterable, want[i].Filterable)
}
if got[i].Sortable != want[i].Sortable {
t.Fatalf("Sortable got:%v want: %v", got[i].Sortable, want[i].Sortable)
}
if got[i].Name != want[i].Name {
t.Fatalf("Name got:%v want: %v", got[i].Name, want[i].Name)
}
}
}