forked from rs/rest-layer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathresource_path.go
162 lines (149 loc) · 4.33 KB
/
resource_path.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
158
159
160
161
162
package rest
import (
"context"
"strings"
"sync"
"github.com/rs/rest-layer/resource"
"github.com/rs/rest-layer/schema"
)
// ResourcePath is the list of ResourcePathComponent leading to the requested resource
type ResourcePath []*ResourcePathComponent
// ResourcePathComponent represents the path of resource and sub-resources of a given request's resource
type ResourcePathComponent struct {
// Name is the endpoint name used to bind the resource
Name string
// Field is the resource's field used to filter targeted resource
Field string
// Value holds the resource's id value
Value interface{}
// Resource references the resource
Resource *resource.Resource
}
var resourcePathComponentPool = sync.Pool{
New: func() interface{} {
return &ResourcePathComponent{}
},
}
// Prepend add the given resource using the provided field and value as a "ghost" resource
// prefix to the resource path.
//
// The effect will be a 404 error if the doesn't have an item with the id matching to the
// provided value.
//
// This will also require that all subsequent resources in the path have this resource's
// "value" set on their "field" field.
//
// Finally, all created resources at this path will also have this field and value set by default.
func (p *ResourcePath) Prepend(rsrc *resource.Resource, field string, value interface{}) {
rp := resourcePathComponentPool.Get().(*ResourcePathComponent)
rp.Field = field
rp.Value = value
rp.Resource = rsrc
// Prepent the resource path with the user resource
*p = append(ResourcePath{rp}, *p...)
}
func (p *ResourcePath) append(rsrc *resource.Resource, field string, value interface{}, name string) (err error) {
if field != "" && value != nil {
if f, found := rsrc.Schema().Fields["id"]; found {
if f.Validator != nil {
value, err = f.Validator.Validate(value)
if err != nil {
return
}
}
}
}
rp := resourcePathComponentPool.Get().(*ResourcePathComponent)
rp.Name = name
rp.Field = field
rp.Value = value
rp.Resource = rsrc
*p = append(*p, rp)
return
}
func (p *ResourcePath) clear() {
for i, rp := range *p {
rp.Name = ""
rp.Field = ""
rp.Value = nil
rp.Resource = nil
resourcePathComponentPool.Put(rp)
(*p)[i] = nil
}
*p = (*p)[:0]
}
// ParentsExist checks if the each intermediate parents in the path exist and
// return either a ErrNotFound or an error returned by on of the intermediate
// resource.
func (p ResourcePath) ParentsExist(ctx context.Context) error {
// First we check that we have no field conflict on the path (i.e.: two path
// components defining the same field with a different value)
fields := map[string]interface{}{}
for _, rp := range p {
if val, found := fields[rp.Field]; found && val != rp.Value {
return &Error{404, "Resource Path Conflict", nil}
}
fields[rp.Field] = rp.Value
}
// Check parents existence
parents := len(p) - 1
if parents <= 0 {
return nil
}
q := schema.Query{}
wait := sync.WaitGroup{}
defer wait.Wait()
c := make(chan error, parents)
for i := 0; i < parents; i++ {
if p[i].Value == nil {
continue
}
// Create a lookup with the parent path fields + the current path id
l := resource.NewLookup()
lq := append(q[:], schema.Equal{Field: "id", Value: p[i].Value})
l.AddQuery(lq)
// Execute all intermediate checkes in concurence
wait.Add(1)
go func(index int) {
defer wait.Done()
// Check if the resource exists
list, err := p[index].Resource.Find(ctx, l, 0, 1)
if err != nil {
c <- err
} else if len(list.Items) == 0 {
c <- &Error{404, "Parent Resource Not Found", nil}
} else {
c <- nil
}
}(i)
// Push the resource field=value for the next hops
q = append(q, schema.Equal{Field: p[i].Field, Value: p[i].Value})
}
// Fail on first error
for i := 0; i < parents; i++ {
if err := <-c; err != nil {
return err
}
}
return nil
}
// Path returns the path to the resource to be used with resource.Root.GetResource
func (p ResourcePath) Path() string {
path := []string{}
for _, c := range p {
if c.Name != "" {
path = append(path, c.Name)
}
}
return strings.Join(path, ".")
}
// Values returns all the key=value pairs defined by the resource path
func (p ResourcePath) Values() map[string]interface{} {
v := map[string]interface{}{}
for _, rp := range p {
if _, found := v[rp.Field]; !found && rp.Value != nil {
v[rp.Field] = rp.Value
}
}
return v
}