forked from rs/rest-layer
/
lookup.go
157 lines (142 loc) · 4.7 KB
/
lookup.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package resource
import (
"context"
"errors"
"fmt"
"strings"
"github.com/rs/rest-layer/schema"
)
// Lookup holds filter and sort used to select items in a resource collection
type Lookup struct {
// The client supplied filter. Filter is a MongoDB inspired query with a more limited
// set of capabilities. See https://github.com/rs/rest-layer#filtering
// for more info.
filter schema.Query
// The client supplied soft. Sort is a list of resource fields or sub-fields separated
// by comas (,). To invert the sort, a minus (-) can be prefixed.
// See https://github.com/rs/rest-layer#sorting for more info.
sort []string
// The client supplied selector. Selector is a way for the client to reformat the
// resource representation at runtime by defining which fields should be included
// in the document. The REST Layer selector language allows field aliasing, field
// transformation with parameters and sub-item/collection embedding.
selector []Field
}
// Field is used with Lookup.selector to reformat the resource representation at runtime
// using a field selection language inspired by GraphQL.
type Field struct {
// Name is the name of the field as define in the resource's schema.
Name string
// Alias is the wanted name in the representation.
Alias string
// Params defines a list of params to be sent to the field's param handler if any.
Params map[string]interface{}
// Fields holds references to child fields if any
Fields []Field
}
// NewLookup creates an empty lookup object
func NewLookup() *Lookup {
return &Lookup{
filter: schema.Query{},
sort: []string{},
}
}
// NewLookupWithQuery creates an empty lookup object with a given query
func NewLookupWithQuery(q schema.Query) *Lookup {
return &Lookup{
filter: q,
sort: []string{},
}
}
// Sort is a list of resource fields or sub-fields separated
// by comas (,). To invert the sort, a minus (-) can be prefixed.
//
// See https://github.com/rs/rest-layer#sorting for more info.
func (l *Lookup) Sort() []string {
return l.sort
}
// Filter is a MongoDB inspired query with a more limited set of capabilities.
//
// See https://github.com/rs/rest-layer#filtering for more info.
func (l *Lookup) Filter() schema.Query {
return l.filter
}
// SetSorts set the sort fields with a pre-parsed list of fields to sort on.
// This method doesn't validate sort fields.
func (l *Lookup) SetSorts(sorts []string) {
l.sort = sorts
}
// SetSort parses and validate a sort parameter and set it as lookup's Sort
func (l *Lookup) SetSort(sort string, v schema.Validator) error {
sorts := []string{}
for _, f := range strings.Split(sort, ",") {
f = strings.Trim(f, " ")
if f == "" {
return errors.New("empty soft field")
}
// If the field start with - (to indicate descended sort), shift it before
// validator lookup
i := 0
if f[0] == '-' {
i = 1
}
// Make sure the field exists
field := v.GetField(f[i:])
if field == nil {
return fmt.Errorf("invalid sort field: %s", f[i:])
}
if !field.Sortable {
return fmt.Errorf("field is not sortable: %s", f[i:])
}
sorts = append(sorts, f)
}
l.sort = sorts
return nil
}
// AddFilter parses and validate a filter parameter and add it to lookup's filter
//
// The filter query is validated against the provided validator to ensure all queried
// fields exists and are of the right type.
func (l *Lookup) AddFilter(filter string, v schema.Validator) error {
f, err := schema.ParseQuery(filter, v)
if err != nil {
return err
}
l.AddQuery(f)
return nil
}
// AddQuery add an existing schema.Query to the lookup's filters
func (l *Lookup) AddQuery(query schema.Query) {
if l.filter == nil {
l.filter = query
return
}
for _, exp := range query {
l.filter = append(l.filter, exp)
}
}
// SetSelector parses a selector expression, validates it and assign it to the current Lookup.
func (l *Lookup) SetSelector(s string, v schema.Validator) error {
pos := 0
selector, err := parseSelectorExpression([]byte(s), &pos, len(s), false)
if err != nil {
return err
}
if err = validateSelector(selector, v); err != nil {
return err
}
l.selector = selector
return nil
}
// ReferenceResolver is a function resolving a reference to another field
type ReferenceResolver func(path string) (*Resource, error)
// ApplySelector applies fields filtering / rename to the payload fields
func (l *Lookup) ApplySelector(ctx context.Context, v schema.Validator, p map[string]interface{}, resolver ReferenceResolver) (map[string]interface{}, error) {
payload, err := applySelector(ctx, l.selector, v, p, resolver)
if err == nil {
// The resulting payload may contain some asyncSelector, we must execute them
// concurrently until there's no more
err = resolveAsyncSelectors(ctx, payload)
}
return payload, err
}