This repository was archived by the owner on Jun 18, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtree.go
216 lines (196 loc) · 5.95 KB
/
tree.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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
// Goro
//
// Created by Yakka
// http://theyakka.com
//
// Copyright (c) 2019 Yakka LLC.
// All rights reserved.
// See the LICENSE file for licensing details and requirements.
package goro
import (
"fmt"
"regexp"
"strings"
)
// Node - tree node to store route information
type Node struct {
part string
nodeType RouteComponentType
regexp *regexp.Regexp // Not used currently
routes map[string]*Route
nodes []*Node
parent *Node
}
// Tree - storage for routes
type Tree struct {
nodes []*Node
}
// NewTree - creates a new Tree instance
func NewTree() *Tree {
return &Tree{
nodes: []*Node{},
}
}
// NewNode - creates a new Node instance and appends it to the tree
func (t *Tree) NewNode(part string, parent *Node) *Node {
nodeType := ComponentTypeFixed
if strings.HasPrefix(part, "*") {
nodeType = ComponentTypeCatchAll
} else if strings.HasPrefix(part, ":") {
nodeType = ComponentTypeWildcard
}
node := &Node{
part: part,
nodeType: nodeType,
regexp: nil,
nodes: []*Node{},
parent: parent,
routes: nil,
}
if parent == nil {
t.nodes = append(t.nodes, node)
} else {
parent.nodes = append(parent.nodes, node)
}
return node
}
// AddRouteToTree - splits the route into Nodes and adds them to the tree
func (t *Tree) AddRouteToTree(route *Route, variables map[string]string) {
path := route.PathFormat
deslashedPath := path
if strings.HasPrefix(deslashedPath, "/") {
deslashedPath = path[1:]
}
split := strings.Split(deslashedPath, "/")
if route.IsRoot() {
node := t.NewNode("/", nil)
if node.routes == nil {
node.routes = map[string]*Route{}
}
node.routes[route.Method] = route
} else {
// check to see if we need to do any variable substitution before parsing
// NOTE: does not support nested variables
var processedSplit []string
for _, component := range split {
if isVariablePart(component) {
deslashedVar := resolveVariable(component, variables, path)
if strings.HasPrefix(deslashedVar, "/") {
deslashedVar = deslashedVar[1:]
}
splitVar := strings.Split(deslashedVar, "/")
processedSplit = append(processedSplit, splitVar...)
} else {
processedSplit = append(processedSplit, component)
}
}
split = processedSplit
var parentNode *Node
var node *Node
for _, component := range split {
// get an existing node for this segment or attach to the tree
node = t.nodeForExactPart(component, parentNode)
if node == nil {
node = t.NewNode(component, parentNode)
}
parentNode = node
}
if node.routes == nil {
node.routes = map[string]*Route{}
}
node.routes[route.Method] = route
}
}
// nodeForExactPart - finds a Node either in the top-level Tree nodes or in the
// children of a parent node (if supplied) that has an exact string match for the
// supplied 'part' value
func (t *Tree) nodeForExactPart(part string, parentNode *Node) *Node {
var nodesToCheck []*Node
if parentNode == nil {
nodesToCheck = t.nodes
} else {
nodesToCheck = parentNode.nodes
}
for _, node := range nodesToCheck {
if node.part == part {
return node
}
}
return nil
}
// HasChildren - returns true if the Node has 1 or more sub-Nodes
func (node *Node) HasChildren() bool {
return node.nodes != nil && len(node.nodes) > 0
}
// RouteForMethod - returns the route that was defined for the method or nil if
// no route is defined
func (node *Node) RouteForMethod(method string) *Route {
if node.routes != nil && len(node.routes) > 0 {
return node.routes[strings.ToUpper(method)]
}
return nil
}
// isVariablePart - is the string (part) a variable part
func isVariablePart(part string) bool {
return strings.HasPrefix(part, "$")
}
// isWildcardPart - is the string (part) a wildcard part
func isWildcardPart(part string) bool {
return strings.HasPrefix(part, ":")
}
// isCatchAllPart - is the string (part) a catch-all part
func isCatchAllPart(part string) bool {
return strings.HasPrefix(part, "*")
}
// containsVariablePrefix - does the string contain a variable prefix value
func containsVariablePrefix(s string) bool {
return strings.Contains(s, "$")
}
// resolveVariable - returns a string with all variables resolved.
// Takes in a string which may contain variables, a map 'variables' used
// for lookup, and a string 'path' to construct panic statement if lookup fails.
func resolveVariable(component string, variables map[string]string, path string) string {
resolved := ""
parts := splitVariableComponent(component)
for _, part := range parts {
// Split parts further to handle "/"s.
deslashedPart := strings.Split(part, "/")
for i, dsp := range deslashedPart {
if containsVariablePrefix(dsp) {
lookup := variables[dsp]
if lookup == "" {
// we couldn't substitute the requested variable as there is no value definition
panic(fmt.Sprintf("Missing variable substitution for '%s'. route='%s'", dsp, path))
}
// Another lookup is required because value definition contains a variable.
if containsVariablePrefix(lookup) {
lookup = resolveVariable(lookup, variables, path)
}
deslashedPart[i] = lookup
}
}
// Recombine deslashed parts after they have been resolved.
resolvedPart := strings.Join(deslashedPart, "/")
resolved = strings.Join([]string{resolved, resolvedPart}, "")
}
return resolved
}
// splitVariableComponent - Splits a string into variables, and non-variable
// strings. For example, "foo$bar$baz" => []string{ "foo", "$bar", "$baz" }
func splitVariableComponent(s string) []string {
var separated []string
delimIndex := strings.LastIndex(s, "$")
if delimIndex == -1 {
// No variable components left to split
separated = []string{s}
} else {
resolveNested := splitVariableComponent(s[:delimIndex])
separated = append(resolveNested, s[delimIndex:])
}
return separated
}
// String - the string representation of the object when printing
func (node *Node) String() string {
return fmt.Sprintf("goro.Node # type=%d, part=%s, children=%d, routes=%v",
node.nodeType, node.part, len(node.nodes), node.routes)
}