UNPKG

7.04 kBJavaScriptView Raw
1'use strict';
2
3// Load modules
4
5const Hoek = require('hoek');
6
7
8// Declare internals
9
10const internals = {};
11
12
13exports = module.exports = internals.Segment = function () {
14
15 this._edge = null; // { segment, record }
16 this._fulls = null; // { path: { segment, record }
17 this._literals = null; // { literal: { segment, <node> } }
18 this._param = null; // <node>
19 this._mixed = null; // [{ segment, <node> }]
20 this._wildcard = null; // { segment, record }
21};
22
23
24internals.Segment.prototype.add = function (segments, record) {
25
26 /*
27 { literal: 'x' } -> x
28 { empty: false } -> {p}
29 { wildcard: true } -> {p*}
30 { mixed: /regex/ } -> a{p}b
31 */
32
33 const current = segments[0];
34 const remaining = segments.slice(1);
35 const isEdge = !remaining.length;
36
37 const literals = [];
38 let isLiteral = true;
39 for (let i = 0; i < segments.length && isLiteral; ++i) {
40 isLiteral = segments[i].literal !== undefined;
41 literals.push(segments[i].literal);
42 }
43
44 if (isLiteral) {
45 this._fulls = this._fulls || {};
46 let literal = '/' + literals.join('/');
47 if (!record.settings.isCaseSensitive) {
48 literal = literal.toLowerCase();
49 }
50
51 Hoek.assert(!this._fulls[literal], 'New route', record.path, 'conflicts with existing', this._fulls[literal] && this._fulls[literal].record.path);
52 this._fulls[literal] = { segment: current, record };
53 }
54 else if (current.literal !== undefined) { // Can be empty string
55
56 // Literal
57
58 this._literals = this._literals || {};
59 const currentLiteral = (record.settings.isCaseSensitive ? current.literal : current.literal.toLowerCase());
60 this._literals[currentLiteral] = this._literals[currentLiteral] || new internals.Segment();
61 this._literals[currentLiteral].add(remaining, record);
62 }
63 else if (current.wildcard) {
64
65 // Wildcard
66
67 Hoek.assert(!this._wildcard, 'New route', record.path, 'conflicts with existing', this._wildcard && this._wildcard.record.path);
68 Hoek.assert(!this._param || !this._param._wildcard, 'New route', record.path, 'conflicts with existing', this._param && this._param._wildcard && this._param._wildcard.record.path);
69 this._wildcard = { segment: current, record };
70 }
71 else if (current.mixed) {
72
73 // Mixed
74
75 this._mixed = this._mixed || [];
76
77 let mixed = this._mixedLookup(current);
78 if (!mixed) {
79 mixed = { segment: current, node: new internals.Segment() };
80 this._mixed.push(mixed);
81 this._mixed.sort(internals.mixed);
82 }
83
84 if (isEdge) {
85 Hoek.assert(!mixed.node._edge, 'New route', record.path, 'conflicts with existing', mixed.node._edge && mixed.node._edge.record.path);
86 mixed.node._edge = { segment: current, record };
87 }
88 else {
89 mixed.node.add(remaining, record);
90 }
91 }
92 else {
93
94 // Parameter
95
96 this._param = this._param || new internals.Segment();
97
98 if (isEdge) {
99 Hoek.assert(!this._param._edge, 'New route', record.path, 'conflicts with existing', this._param._edge && this._param._edge.record.path);
100 this._param._edge = { segment: current, record };
101 }
102 else {
103 Hoek.assert(!this._wildcard || !remaining[0].wildcard, 'New route', record.path, 'conflicts with existing', this._wildcard && this._wildcard.record.path);
104 this._param.add(remaining, record);
105 }
106 }
107};
108
109
110internals.Segment.prototype._mixedLookup = function (segment) {
111
112 for (let i = 0; i < this._mixed.length; ++i) {
113 if (internals.mixed({ segment }, this._mixed[i]) === 0) {
114 return this._mixed[i];
115 }
116 }
117
118 return null;
119};
120
121
122internals.mixed = function (a, b) {
123
124 const aFirst = -1;
125 const bFirst = 1;
126
127 const as = a.segment;
128 const bs = b.segment;
129
130 if (as.length !== bs.length) {
131 return (as.length > bs.length ? aFirst : bFirst);
132 }
133
134 if (as.first !== bs.first) {
135 return (as.first ? bFirst : aFirst);
136 }
137
138 for (let i = 0; i < as.segments.length; ++i) {
139 const am = as.segments[i];
140 const bm = bs.segments[i];
141
142 if (am === bm) {
143 continue;
144 }
145
146 if (am.length === bm.length) {
147 return (am > bm ? bFirst : aFirst);
148 }
149
150 return (am.length < bm.length ? bFirst : aFirst);
151 }
152
153 return 0;
154};
155
156
157internals.Segment.prototype.lookup = function (path, segments, options) {
158
159 let match = null;
160
161 // Literal edge
162
163 if (this._fulls) {
164 match = this._fulls[options.isCaseSensitive ? path : path.toLowerCase()];
165 if (match) {
166 return { record: match.record, array: [] };
167 }
168 }
169
170 // Literal node
171
172 const current = segments[0];
173 const nextPath = path.slice(current.length + 1);
174 const remainder = (segments.length > 1 ? segments.slice(1) : null);
175
176 if (this._literals) {
177 const literal = options.isCaseSensitive ? current : current.toLowerCase();
178 match = this._literals.hasOwnProperty(literal) && this._literals[literal];
179 if (match) {
180 const record = internals.deeper(match, nextPath, remainder, [], options);
181 if (record) {
182 return record;
183 }
184 }
185 }
186
187 // Mixed
188
189 if (this._mixed) {
190 for (let i = 0; i < this._mixed.length; ++i) {
191 match = this._mixed[i];
192 const params = current.match(match.segment.mixed);
193 if (params) {
194 const array = [];
195 for (let j = 1; j < params.length; ++j) {
196 array.push(params[j]);
197 }
198
199 const record = internals.deeper(match.node, nextPath, remainder, array, options);
200 if (record) {
201 return record;
202 }
203 }
204 }
205 }
206
207 // Param
208
209 if (this._param) {
210 if (current ||
211 (this._param._edge && this._param._edge.segment.empty)) {
212
213 const record = internals.deeper(this._param, nextPath, remainder, [current], options);
214 if (record) {
215 return record;
216 }
217 }
218 }
219
220 // Wildcard
221
222 if (this._wildcard) {
223 return { record: this._wildcard.record, array: [path.slice(1)] };
224 }
225
226 return null;
227};
228
229
230internals.deeper = function (match, path, segments, array, options) {
231
232 if (!segments) {
233 if (match._edge) {
234 return { record: match._edge.record, array };
235 }
236
237 if (match._wildcard) {
238 return { record: match._wildcard.record, array };
239 }
240 }
241 else {
242 const result = match.lookup(path, segments, options);
243 if (result) {
244 return { record: result.record, array: array.concat(result.array) };
245 }
246 }
247
248 return null;
249};